Autorizacao e permissoes — Arduino e IoT — semana 3 do 3o trimestre

Informatica · Conteudo · publicado em 02/10/2026
Semana 3 · Autorizacao e permissoes — Material de Apoio Arduino

Semana 3 de 15· 3o trimestre · 05/09 a 10/12

Autorizacao e permissoes

Estar logado não e o mesmo que poder.

Aula 1 — Papel do usuario e a tabela de permissoes

Objetivos

  • Separar autenticação de autorização com dois casos concretos do projeto, e dizer o que cada uma responde.
  • Escrever a tabela de papéis e a tabela de permissões, e decidir onde fica a relação entre usuário e papel.
  • Conceder e revogar uma permissão pelo caminho que o projeto usa, e medir o efeito de cada uma no menu do painel.
  • Bloquear a escalada de privilégio do cliente, mostrando qual requisição tenta pedir papel de administrador.
  • Ler no monitor serial as três consultas que a placa executa sobre permissão, e dizer qual delas o servidor responde.

Material

  • 1 ESP32 DevKit V1 por dupla, com o cabo USB
  • 1 LED da placa no GPIO2, com o resistor de 330 ohm da bancada
  • 1 DHT11 por dupla, com resistor de pull-up de 4,7 k ohm entre o pino de dados e 3V3
  • 1 computador com o projeto do 2o trimestre aberto, nos arquivos de usuário e de rota
  • Folha de papel por dupla, com a matriz papel por permissão
  • Projetor, para o terminal do Node e o monitor serial da placa juntos

Conceitos

Autorização é uma pergunta diferente, com resposta diferente

Autorização é a pergunta que o sistema faz depois da autenticação: o que esta pessoa específica pode fazer. A pergunta anterior era "quem é você", e a resposta certa era o identificador. Agora a pergunta é outra, e a resposta não é um identificador: é uma lista de coisas permitidas.

A distinção fica nítida com um caso que toda a turma já usou: o painel tem duas telas, uma de leitura e outra de configuração. A pessoa está autenticada nas duas — mesmo token, mesma sessão, mesmo nome no canto da tela. O que muda é o botão que aparece, e o que muda no servidor é a resposta quando alguém pede a tela de configuração sem permissão. Um sistema que resolve as duas perguntas com o mesmo mecanismo não tem autorização: tem uma autenticação que resolve duas vezes e erra uma.

A permissão é a unidade de autorização, e é a menor coisa que o sistema diz que pode ser feita. Ela é um verbo: *ver a leitura*, *mudar a configuração da estação*, *cadastrar usuário*, *apagar histórico*. O papel é um conjunto de permissões com um nome. A razão de existirem os dois é de edição: ninguém quer editar "pode ver a leitura" em treze lugares, e todo mundo quer editar "o leitor pode ver a leitura" em um lugar só.

O role é o mesmo conceito com o nome em inglês, e ele aparece em quase toda documentação de sistema web. O perfil também é sinônimo, com a agravante de que em português a palavra é usada para outra coisa — o perfil do usuário é um dado, não um conjunto de permissões. Quando a turma escreve "perfil do administrador" querendo dizer "o que o administrador pode fazer", ela está misturando os dois e o código fica ambíguo.

ConceitoPergunta que respondeOnde fica guardado
autenticaçãoquem é vocêa sessão, com o identificador
permissãoo que esta pessoa podea tabela de permissões
papelo conjunto de permissõesa tabela de papéis
roleo papel, em inglêsa mesma tabela, com o nome em inglês no código

Os três papéis do projeto e a matriz que sai da conversa

O projeto do curso usa três papéis, e o professor os escreve na lousa antes de qualquer código, com a justificativa de cada um:

  • administrador: configura a estação, cadastra usuário, vê e apaga histórico. É quem decide o resto.
  • editor: configura a estação e vê histórico, mas não cadastra usuário nem apaga nada.
  • leitor: vê a leitura atual e o histórico, e não altera nada.

O usuário comum é o sinônimo informal de leitor, e vale a pena insistir na distinção: "usuário comum" descreve uma posição na hierarquia, "leitor" descreve um conjunto de permissões. A hierarquia é mais simples de falar e mais difícil de manter, porque basta um novo papel aparecer para que "comum" deixe de significar nada.

A matriz que o professor preenche com a turma é esta, e cada casa marcada é uma permissão que existe:

Permissãoleitoreditoradministrador
ver leitura atualsimsimsim
ver históricosimsimsim
mudar configuração da estaçãonãosimsim
cadastrar usuárionãonãosim
apagar históriconãonãosim
revogar permissãonãonãosim

A linha que a turma mais estranha é a última. Quem pode revogar permissão é quem pode conceder, e a tabela tem que deixar isso explícito, porque a consequência de errar essa linha é que um administrador fica preso no sistema — sem caminho para sair de um papel que alguém concedeu por engano.

Existem três formas de modelar isso, e o professor mostra as três antes de escolher. O que muda entre elas é o quanto trabalho aparece quando alguém pede um papel novo.

A forma mais simples é uma coluna de papel na tabela de usuário: admin, editor ou leitor. Custa uma coluna e resolve o primeiro dia. Em duas semanas, o pedido é "e se o professor puder ver tudo e mudar só a própria estação?", e a coluna não tem como responder. O defeito dela é não permitir dois papéis ao mesmo tempo.

A forma intermediária é a tabela de papéis com uma lista de permissões dentro de cada linha. Aceita vários papéis por pessoa, desde que a consulta una as permissões. A desvantagem é que comparar permissões passa a ser comparação de texto.

A forma que o projeto usa é a terceira: tabela de papéis e tabela de permissões separadas, com uma tabela que liga as duas. Cada papel aponta para um conjunto de permissões guardado em uma coluna, e a consulta do servidor é uma junção. Ela custa três tabelas em vez de uma coluna, e em troca responde a qualquer pergunta que aparecer depois — inclusive a do professor que quer ver tudo e mudar só a própria estação.

A relação entre usuário e papel é o ponto que costuma ser feito errado, e o erro tem nome: a coluna de papel no usuário guarda uma lista separada por vírgula. A partir daí, responder "o administrador tem a permissão de apagar histórico" exige procurar dentro de uma string, a consulta deixa de usar índice, e o sistema passa a depender do formato do texto. Papel é entidade, e entidade tem linha.

O que decide onde o papel fica no momento da requisição é a próxima seção, e a resposta tem consequências de segurança.

Onde fica o papel, no token ou no banco

Existem dois lugares possíveis para o papel, e a escolha tem um custo que o professor mede em voz alta.

O papel no banco é a fonte da verdade: a cada requisição, o servidor lê o papel do usuário no banco e decide. O custo é uma consulta extra por requisição, mesmo quando o token já está na mão. O benefício é que revogar um papel tem efeito imediato, na próxima requisição.

O papel no token é uma cópia: quando o token é emitido, o papel vai dentro dele, e o servidor confia no que veio no token sem consultar nada. O custo é zero por requisição. O benefício é zero em revisão, e o defeito é nomeado: um token emitido antes da revogação continua valendo depois dela. Se o professor remove o papel de administrador de alguém às 10h, o token daquela pessoa continua dizendo administrador até as 12h, que é quando expira.

A resposta que o projeto usa é a intermediária, e ela vale a pena porque é a mesma do dia 2: o token diz quem é, o banco diz o que pode. A placa não precisa carregar o papel, e o servidor não precisa confiar no papel que veio no pedido. O que o token carrega é o identificador, e o papel é sempre consultado.

O que a placa faz nessa aula é mostrar as três consultas, e o professor destaca a ordem. A placa pergunta ao servidor o que a sessão atual pode fazer, recebe a lista, e imprime. A consulta do papel é do servidor, e a placa jamais decide nem um item daquela lista. Se a placa imprimir "o painel tem o botão de apagar" porque a resposta trouxe essa permissão, tudo certo; se a placa imprimir porque o firmware tem isso escrito, a permissão é enfeite.

Escalada de privilégio: a requisição que pede para virar administrador

A escalada de privilégio é o ataque em que alguém obtém mais permissão do que deveria ter, e a forma mais comum é a mais boba: o cliente pede.

A tentativa — um cliente pede papel de admin — é sempre a mesma, e o professor a demonstra com o painel aberto. A pessoa está autenticada como leitor, e o pedido de configuração vai para o servidor com o papel que o navegador permite editar. A equipe que escreveu o frontend dias antes deixou o campo no formulário porque "só para o usuário não ver o campo escondido", e o valor padrão gravado é admin. A requisição chega ao servidor pedindo papel de administrador, e a resposta depende inteiramente do servidor.

Quando o servidor confia, o sistema tem uma falha que nenhum teste de interface detecta, porque a interface está correta: o botão está escondido, o campo não aparece, e a requisição foi montada por um humano com o inspetor do navegador aberto. Por isso o professor repete a regra do dia 1 com outras palavras: quem produz o dado não pode ser quem decide que o dado é válido. O papel é produzido pelo cliente, e por isso o cliente não pode escolher o papel.

Duas defesas, e a segunda é a que a turma esquece. A primeira é o servidor ignorar o campo de papel e usar o que está na sessão. A segunda é a conceder permissão happening pelo caminho do servidor: um administrador concede, e o sistema registra quem concedeu, para quem e quando. A segunda existe porque sem registro não há como responder "quem deu esse acesso a essa pessoa", e essa é a primeira pergunta de qualquer auditoria.

Revogar permissão é a operação espelho, com a mesma exigência de registro. E tem uma consequência que o professor aponta: revogar não apaga o token, ele encurta o efeito do token. Quem tinha o papel até 10h continua com o papel até o token expirar, e a diferença entre as duas coisas é exatamente a validade de duas horas do dia 2.

Atividade

Montagem:

  • DHT11 no GPIO4, com pull-up de 4,7 k ohm entre o pino de dados e 3V3.
  • LED da placa no GPIO2, com o resistor de 330 ohm e o jumper para o GND.
  • Cabo USB conectado, monitor serial em 115200.
  • Servidor Node do 2o trimestre rodando, com as tabelas de usuário e de sessão criadas.
  1. Desenhe a matriz papel por permissão com os três papéis do projeto e as seis permissões do Conceitos. Preencha cada casa com sim ou não. Antes de preencher, escreva em uma frase qual é a sua regra para decidir cada casa.
  2. No seu projeto, abra o arquivo de usuário e escreva o nome da coluna ou da tabela que guarda o papel. Ela é uma coluna com texto, uma lista separada por vírgula, ou uma tabela que liga as duas? Copie a linha exata do código.
  3. Grave o sketch da resolução e rode no monitor serial por um minuto. Copie as três consultas que ele executa, na ordem em que aparecem, e o resultado de cada uma.
  4. Escreva no papel quantas vezes o sketch consultou o servidor e quantas vezes consultou a si mesmo sobre permissão. Escreva em uma frase quem decidiu, em cada caso, o que a pessoa podia fazer.
  5. No seu projeto, troque o papel da sua sessão para editor e recarregue o painel. Antes de recarregar, escreva o que você espera ver. Depois, escreva o que apareceu. O número mudou, ou só os botões?
  6. Abra o inspetor do navegador na aba de rede, procure a requisição de configuração e edite o campo de papel para admin. Envie. Escreva o que o servidor respondeu e em qual linha do seu código essa resposta é decidida.
  7. Procure no seu projeto todas as rotas que mudam alguma coisa. Para cada uma, escreva o nome do arquivo, o número da linha e qual papel ela exige. Quantas dessas rotas você forgotten de proteger? A lista inteira é a base do item 3 da aula 2.
  8. Escreva em uma frase por linha as respostas de duas perguntas: por que o papel no token não revoga na hora, e o que a placa precisaria fazer para que o painel escondesse um botão de verdade.

Nota: 10 pontos. Critério de fim: a matriz de seis permissões preenchida, as três consultas do sketch copiadas com o resultado, e a resposta do servidor à requisição com o papel editado anotada.

Resolucao

O sketch da aula está em codigo/t3/dia03/aula1.ino.

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// Rede do laboratorio. Os dois valores sao FICTICIOS de proposito: nenhum
// segredo real entra em sketch de exemplo, nem em material, nem no git.
#define WIFI_SSID "lab-esp32-aula"
#define WIFI_PASSWORD "troque-esta-pela-sua"

#define URL_PERMISSOES "http://192.168.0.10:3000/api/permissoes"
#define URL_PAPEL     "http://192.168.0.10:3000/api/papel"

// O token NAO e uma senha: e o que o servidor devolveu no login. Ele vai no
// cabecalho, nunca na URL. Em HTTP em claro ele viaja a vista, que e o motivo
// de o transporte do projeto ser HTTPS.
const char* TOKEN_DE_EXEMPLO = "tk-lab-bancada01";

const int PIN_LED = 2;
const unsigned long INTERVALO_MS = 6000;

// ---------------------------------------------------------------------------
// O QUE A PLACA GUARDA E O QUE A PLACA NAO GUARDA
//
// A placa guarda o token e o espelho do que o servidor autorizou. A placa
// nao guarda o papel: se guardasse, bastaria trocar um texto no firmware para
// virar administrador. Toda vez que a lista muda, a placa pergunta de novo.
// ---------------------------------------------------------------------------

// Espelho local das permissoes. E cache de DECISAO, e o valor guardado em
// espaco de memoria persistente, nao a fonte: a fonte e a resposta do servidor.
const char* PERMISSOES_SERVIDOR =
  "ver_leitura_atual,ver_historico,mudar_configuracao";

// O que o navegador esconde e o que o servidor autoriza nao sao a mesma coisa.
// Este arranjo e o que o painel faria com a lista, e existe para a aula mostrar
// que esconder botao nao e seguranca.
struct Botao {
  const char* rotulo;
  const char* permissao;   // qual permissao da lista ele exige
};

const Botao BOTOES[] = {
  {"ver leitura atual",   "ver_leitura_atual"},
  {"ver historico",       "ver_historico"},
  {"mudar configuracao",  "mudar_configuracao"},
  {"cadastrar usuario",   "cadastrar_usuario"},   // o leitor nao tem esta
  {"apagar historico",    "apagar_historico"}     // o leitor nao tem esta
};
const int TOTAL_BOTOES = 5;

// ---------------------------------------------------------------------------
// As tres consultas que o sketch executa, uma por vez.
//
// A ordem e o conteudo da aula. A placa pergunta; o servidor responde; a
// placa guarda o espelho. Nenhuma delas decide o que a pessoa pode fazer.
// ---------------------------------------------------------------------------

// 1. Quem eu sou, segundo o servidor. Responde com o papel, nunca com a lista
//    de permissoes: a lista muda mais vezes que o papel, e token com papel
//    fixo envelhece mal.
String consultarPapel() {
  HTTPClient http;
  http.setTimeout(4000);
  http.begin(URL_PAPEL);
  http.addHeader("Authorization", TOKEN_DE_EXEMPLO);
  int status = http.GET();
  String corpo = (status > 0 && status < 300) ? http.getString() : String("");
  http.end();

  if (status == 401) return "SEM SESSAO";
  if (status == 403) return "SEM PERMISSAO";
  if (status != 200) return "ERRO";
  return corpo;
}

// 2. O que essa pessoa pode. A resposta e a unica fonte da verdade de
//    permissao do sistema, e ela e consultada a cada ciclo.
String consultarPermissoes() {
  HTTPClient http;
  http.setTimeout(4000);
  http.begin(URL_PERMISSOES);
  http.addHeader("Authorization", TOKEN_DE_EXEMPLO);
  int status = http.GET();
  String corpo = (status > 0 && status < 300) ? http.getString() : String("");
  http.end();

  if (status == 200) return corpo;
  if (status == 403) return "";
  return "";
}

// 3. A escalada de privilegio, reproduzida de proposito. A placa envia um
//    pedido que PEDE o papel de administrador. O servidor tem que recusar, e
//    a frase que volta e o que prova que a permissao nao e enfeite.
String tentarEscalarParaAdmin() {
  HTTPClient http;
  http.setTimeout(4000);
  http.begin(URL_PAPEL);
  http.addHeader("Authorization", TOKEN_DE_EXEMPLO);
  http.addHeader("Content-Type", "application/json");
  int status = http.POST("{\"papel\":\"admin\"}");
  String corpo = (status > 0 && status < 300) ? http.getString() : String("");
  http.end();

  if (status == 403) return "RECUSADO: o papel vem da sessao, nao do pedido";
  if (status == 200) return "ACEITO: FALHA CRITICA, a placa virou admin";
  return "ERRO";
}

// O que o painel faria com a lista. Mostrar e conveniencia; proteger e do servidor.
void mostrarBotoes(const String& permissoes) {
  for (int i = 0; i < TOTAL_BOTOES; i++) {
    bool tem = permissoes.indexOf(BOTOES[i].permissao) >= 0;
    Serial.print("    ");
    Serial.print(BOTOES[i].rotulo);
    Serial.print(" : ");
    Serial.println(tem ? "visivel" : "escondido");
  }
}

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  delay(200);

  Serial.println();
  Serial.println("=== dia 3, aula 1: papel e permissao ===");
  Serial.println("A placa decide o que exibir. Quem autoriza e o servidor.");
  Serial.println();
}

void loop() {
  digitalWrite(PIN_LED, HIGH);

  Serial.println("--- 1. quem eu sou ---");
  String papel = consultarPapel();
  Serial.print("  papel informado pelo servidor: ");
  Serial.println(papel);
  Serial.println("  o papel NAO veio no pedido. Se viesse, o cliente escolheria.");
  Serial.println();

  Serial.println("--- 2. o que essa pessoa pode ---");
  String permissoes = consultarPermissoes();
  Serial.print("  lista do servidor: ");
  Serial.println(permissoes.length() ? permissoes : String("(vazia)"));
  Serial.println("  esta lista e a fonte da verdade. A placa guarda so o espelho.");
  Serial.println();

  Serial.println("--- 3. o que o painel faria com a lista ---");
  mostrarBotoes(permissoes);
  Serial.println("  esconder botao e trabalho de tela, NAO e seguranca.");
  Serial.println("  quem protege e a rota, no servidor, que a aula 2 escreve.");
  Serial.println();

  Serial.println("--- 4. a escalada de privilegio ---");
  Serial.print("  ");
  Serial.println(tentarEscalarParaAdmin());
  Serial.println();

  digitalWrite(PIN_LED, LOW);
  delay(INTERVALO_MS);
}

Por que assim e não de outro jeito. A placa não guarda o papel, e essa é a decisão que organiza o sketch inteiro. Se o papel estivesse gravado no firmware, trocar leitor por admin seria trocar uma palavra no binário, e a autorização inteira do sistema dependeria de um texto que o dono da placa controla. Guardando só o token, a placa tem que perguntar, e perguntar significa que o servidor tem a palavra final.

A ordem das três consultas é o roteiro da aula. Primeiro *quem eu sou*, depois *o que essa pessoa pode*, e por último a tentativa de escalada. A inversão de qualquer duas muda o que a demonstração prova: perguntar as permissões antes do papel faz a primeira tela mostrar botões sem dizer de quem, e a escalada por último perde o contraste — o aluno precisa ver a recusa logo depois de ver o que ele já tinha.

O tratamento do status 403 é o que separa um sketch honesto de um sketch que mente. A consultarPermissoes devolve a string vazia quando o servidor recusa, e não a lista que estava em cache da última consulta. A alternativa — manter a última lista conhecida e torcer — é o erro que o dia 11 do primeiro trimestre já mostrou: a placa continua mostrando o botão de apagar para alguém que já perdeu o direito, e ninguém descobre até alguém clicar.

A mostrarBotoes é a parte que prova a tese da aula, e ela é propositalmente incompleta: ela esconde o botão e nada mais. Se a placa quisesse proteger de verdade, teria que recusar a requisição, e a placa não tem como recusar nada — quem faz a requisição é o navegador, e a resposta vem do servidor. Escrever a função de exibição sem a de bloqueio é o que deixa a aula dizer, com o código na mão, que interface mente.

A tentarEscalarParaAdmin devolve três frases diferentes para três respostas diferentes, e a do meio é a que o professor quer que a turma leia duas vezes: se o servidor devolver 200 para um pedido que pede admin, a autorização do sistema é decorativa e o projeto tem de ser refeito. O sketch está preparado para mostrar essa falha, e é por isso que ele existe.

O LED acende e apaga a cada volta do loop como sinal de vida, e a pausa de seis segundos é para o professor ler a tela inteira em frente à turma sem precisar pausar. Nenhum dos dois números é decisão técnica do projeto; os dois são de aula, e o professor diz isso.

Criterios de correcao

CritérioPontos
Matriz de três papéis por seis permissões preenchida, com a regra escrita2 pontos
O lugar do papel no projeto identificado, com arquivo, linha e o formato1 ponto
As três consultas do sketch copiadas, com o resultado de cada uma2 pontos
Item 4: quantas consultas foram ao servidor e quantas ficaram na placa1 ponto
Item 5: o que mudou no painel ao trocar para editor, com o esperado e o obtido1 ponto
A resposta do servidor à requisição com o papel editado, e a linha do código que decide2 pontos
A lista das rotas que mudam algo, com arquivo e linha de cada uma1 ponto

Erros comuns

ErroComo apareceCorreção
Papel em coluna de texto com lista separada por vírgulapapeis = 'editor,leitor' e a consulta procurando por substring"Papel é entidade e tem linha. Com lista em texto, a consulta de permissão deixa de usar índice e o sistema passa a depender do separador que você escolheu."
Confiar no campo de papel que vem no pedidoconst { papel } = req.body; e o papel usado na decisão"O papel vem da sessão, nunca do corpo do pedido. Se o cliente escolhe o próprio papel, não existe autorização — existe uma caixa de seleção."
Esconder o botão e chamar isso de permissãoO formulário não tem o campo, e o teste passa"A interface mente. O botão escondido não protege: quem sabe onde o botão estava procura em cinco segundos. Quem protege é a rota, no servidor."
Papel fixo dentro do token, sem consultar o bancoO token carrega papel e o servidor lê o que veio"Token diz quem é; banco diz o que pode. Com papel no token, revogar leva o tempo da validade inteira para valer, e ninguém sabe dizer quando a revogação surtiu efeito."
Permitir papel no corpo do POST de configuraçãoO aluno envia admin e o banco grava"Conceder permissão é operação de administrador, feita por rota de administrador, e registrada com quem concedeu, para quem e quando. Não é um campo de formulário."
Recusar com 401 o que devia recusar com 403O painel manda a pessoa para a tela de login quando o problema é permissão"401 é "não sei quem você é"; 403 é "sei quem você é e você não pode". Confundir os dois faz a pessoa logar de novo e ter o mesmo problema."
Uma tabela só com o nome do papel, sem as permissõesA tabela de papéis tem duas colunas: nome e descrição"Papel sem lista de permissõesattached é um rótulo. A consulta precisa de uma relação entre papel e permissão, e a relação é o que dá nome ao botão no painel."
Esconder o botão e esconder a rota ao mesmo tempo, e testar só pela interfaceA rota nunca foi testada por fora"Teste de permissão tem dois lados: o positivo, que entra, e o negativo, que não entra. Se você só testou pela interface, você testou o botão, não a rota."

Desafio extra

Escreva a rota que responde "o que este usuário pode fazer" para o seu projeto, e meça quanto tempo ela leva com a tabela de três papéis e com vinte. Depois escreva a versão que devolve só um booleano por permissão, com a forma {"ver_historico": true, "apagar_historico": false}, e meça de novo. Anote os dois números e calcule o quanto a resposta maior custa em bytes. Em seguida, escreva a rota que concede permissão, com o registro de quem concedeu, para quem e quando, e diga em uma frase o que essa linha de registro impede que o dia 8 vai precisar para achar um erro.

>

A resolucao, compilada

// Aula 1 do dia 3 do 3o trimestre de ESP32 e IoT.
// Gerado a partir da secao ## Resolucao de conteudo/t3/dia03/aula1.md.
// Se mudar o codigo, mude no .md e regere: o .ino e copia do .md.

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// Rede do laboratorio. Os dois valores sao FICTICIOS de proposito: nenhum
// segredo real entra em sketch de exemplo, nem em material, nem no git.
#define WIFI_SSID "lab-esp32-aula"
#define WIFI_PASSWORD "troque-esta-pela-sua"

#define URL_PERMISSOES "http://192.168.0.10:3000/api/permissoes"
#define URL_PAPEL     "http://192.168.0.10:3000/api/papel"

// O token NAO e uma senha: e o que o servidor devolveu no login. Ele vai no
// cabecalho, nunca na URL. Em HTTP em claro ele viaja a vista, que e o motivo
// de o transporte do projeto ser HTTPS.
const char* TOKEN_DE_EXEMPLO = "tk-lab-bancada01";

const int PIN_LED = 2;
const unsigned long INTERVALO_MS = 6000;

// ---------------------------------------------------------------------------
// O QUE A PLACA GUARDA E O QUE A PLACA NAO GUARDA
//
// A placa guarda o token e o espelho do que o servidor autorizou. A placa
// nao guarda o papel: se guardasse, bastaria trocar um texto no firmware para
// virar administrador. Toda vez que a lista muda, a placa pergunta de novo.
// ---------------------------------------------------------------------------

// Espelho local das permissoes. E cache de DECISAO, e o valor guardado em
// espaco de memoria persistente, nao a fonte: a fonte e a resposta do servidor.
const char* PERMISSOES_SERVIDOR =
  "ver_leitura_atual,ver_historico,mudar_configuracao";

// O que o navegador esconde e o que o servidor autoriza nao sao a mesma coisa.
// Este arranjo e o que o painel faria com a lista, e existe para a aula mostrar
// que esconder botao nao e seguranca.
struct Botao {
  const char* rotulo;
  const char* permissao;   // qual permissao da lista ele exige
};

const Botao BOTOES[] = {
  {"ver leitura atual",   "ver_leitura_atual"},
  {"ver historico",       "ver_historico"},
  {"mudar configuracao",  "mudar_configuracao"},
  {"cadastrar usuario",   "cadastrar_usuario"},   // o leitor nao tem esta
  {"apagar historico",    "apagar_historico"}     // o leitor nao tem esta
};
const int TOTAL_BOTOES = 5;

// ---------------------------------------------------------------------------
// As tres consultas que o sketch executa, uma por vez.
//
// A ordem e o conteudo da aula. A placa pergunta; o servidor responde; a
// placa guarda o espelho. Nenhuma delas decide o que a pessoa pode fazer.
// ---------------------------------------------------------------------------

// 1. Quem eu sou, segundo o servidor. Responde com o papel, nunca com a lista
//    de permissoes: a lista muda mais vezes que o papel, e token com papel
//    fixo envelhece mal.
String consultarPapel() {
  HTTPClient http;
  http.setTimeout(4000);
  http.begin(URL_PAPEL);
  http.addHeader("Authorization", TOKEN_DE_EXEMPLO);
  int status = http.GET();
  String corpo = (status > 0 && status < 300) ? http.getString() : String("");
  http.end();

  if (status == 401) return "SEM SESSAO";
  if (status == 403) return "SEM PERMISSAO";
  if (status != 200) return "ERRO";
  return corpo;
}

// 2. O que essa pessoa pode. A resposta e a unica fonte da verdade de
//    permissao do sistema, e ela e consultada a cada ciclo.
String consultarPermissoes() {
  HTTPClient http;
  http.setTimeout(4000);
  http.begin(URL_PERMISSOES);
  http.addHeader("Authorization", TOKEN_DE_EXEMPLO);
  int status = http.GET();
  String corpo = (status > 0 && status < 300) ? http.getString() : String("");
  http.end();

  if (status == 200) return corpo;
  if (status == 403) return "";
  return "";
}

// 3. A escalada de privilegio, reproduzida de proposito. A placa envia um
//    pedido que PEDE o papel de administrador. O servidor tem que recusar, e
//    a frase que volta e o que prova que a permissao nao e enfeite.
String tentarEscalarParaAdmin() {
  HTTPClient http;
  http.setTimeout(4000);
  http.begin(URL_PAPEL);
  http.addHeader("Authorization", TOKEN_DE_EXEMPLO);
  http.addHeader("Content-Type", "application/json");
  int status = http.POST("{\"papel\":\"admin\"}");
  String corpo = (status > 0 && status < 300) ? http.getString() : String("");
  http.end();

  if (status == 403) return "RECUSADO: o papel vem da sessao, nao do pedido";
  if (status == 200) return "ACEITO: FALHA CRITICA, a placa virou admin";
  return "ERRO";
}

// O que o painel faria com a lista. Mostrar e conveniencia; proteger e do servidor.
void mostrarBotoes(const String& permissoes) {
  for (int i = 0; i < TOTAL_BOTOES; i++) {
    bool tem = permissoes.indexOf(BOTOES[i].permissao) >= 0;
    Serial.print("    ");
    Serial.print(BOTOES[i].rotulo);
    Serial.print(" : ");
    Serial.println(tem ? "visivel" : "escondido");
  }
}

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  delay(200);

  Serial.println();
  Serial.println("=== dia 3, aula 1: papel e permissao ===");
  Serial.println("A placa decide o que exibir. Quem autoriza e o servidor.");
  Serial.println();
}

void loop() {
  digitalWrite(PIN_LED, HIGH);

  Serial.println("--- 1. quem eu sou ---");
  String papel = consultarPapel();
  Serial.print("  papel informado pelo servidor: ");
  Serial.println(papel);
  Serial.println("  o papel NAO veio no pedido. Se viesse, o cliente escolheria.");
  Serial.println();

  Serial.println("--- 2. o que essa pessoa pode ---");
  String permissoes = consultarPermissoes();
  Serial.print("  lista do servidor: ");
  Serial.println(permissoes.length() ? permissoes : String("(vazia)"));
  Serial.println("  esta lista e a fonte da verdade. A placa guarda so o espelho.");
  Serial.println();

  Serial.println("--- 3. o que o painel faria com a lista ---");
  mostrarBotoes(permissoes);
  Serial.println("  esconder botao e trabalho de tela, NAO e seguranca.");
  Serial.println("  quem protege e a rota, no servidor, que a aula 2 escreve.");
  Serial.println();

  Serial.println("--- 4. a escalada de privilegio ---");
  Serial.print("  ");
  Serial.println(tentarEscalarParaAdmin());
  Serial.println();

  digitalWrite(PIN_LED, LOW);
  delay(INTERVALO_MS);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 3 dia03 aula1.

Aula 2 — Checar permissao em cada rota, e não so no menu

Objetivos

  • Aplicar a checagem de permissão em cada rota do servidor, e listar as rotas do projeto que hoje não têm checagem nenhuma.
  • Escrever o middleware que centraliza a checagem, e explicar por que a centralização é a forma de não esquecer a próxima rota.
  • Montar a tabela de rota por papel, e usá-la para dizer quantas rotas existem para cada papel sem abrir o código.
  • Escrever o teste de permissão com o caso positivo e o caso negativo, e rodar os dois na frente da turma.
  • Ler no monitor serial a resposta 403 de uma rota protegida e a 200 da mesma rota com o papel certo, e dizer qual linha produz cada uma.

Material

  • 1 ESP32 DevKit V1 por dupla, com o cabo USB
  • 1 LED da placa no GPIO2, com o resistor de 330 ohm da bancada
  • 1 computador com o projeto do 2o trimestre aberto, nos arquivos de rota
  • Folha de papel por dupla, com a tabela de rota por papel
  • 1 computador com o monitor serial aberto a 115200
  • Projetor, para o terminal do Node e o monitor serial da placa juntos

Conceitos

Interface mente, e isso não é defeito do programador

O caso que abre a aula é o mais COMMON do curso e já apareceu duas vezes em outros trimestres. O botão de apagar histórico aparece no painel para o administrador e não aparece para o leitor. A equipe chama isso de proteção, o teste passa, e o sistema está aberto.

A frase que o professor escreve na lousa é curta e serve para o ano inteiro: botão escondido não protege. O botão escondido é uma decisão de apresentação. A proteção é uma decisão do servidor, e ela precisa existir mesmo quando o botão não existe — que é justamente o caso em que ninguém testou.

O ataque é elementar e a turma o executa ao vivo. Com o painel aberto numa sessão de leitor, o inspetor do navegador mostra a aba de rede, e a rota de apagar histórico aparece na lista de requisições feitas quando o painel carregou. Chamar a rota com o mesmo token responde com sucesso. Nenhuma ferramenta cara foi necessária: só um botão que alguém um dia decidiu esconder.

Há uma segunda versão do mesmo erro, e ela custa mais caro: a equipe esconde o botão e apaga a rota do menu e acredita que acabou. Apagar do menu é o mesmo que esconder, com mais trabalho. A única coisa que fecha o caminho é a checagem dentro do caminho, e é o que a próxima seção escreve.

Rota protegida e a checagem que mora dentro dela

Uma rota protegida é uma rota que recusa quem não tem a permissão. A definição é simples; o que exige atenção é onde a recusa acontece.

A forma que funciona é a checagem dentro do caminho, antes de qualquer trabalho. A rota pergunta quem é a pessoa, pergunta o que ela pode, compara com o que a rota exige, e só então faz o que foi pedida. Nessa forma, não existe caminho dentro do programa que chegue ao trabalho sem passar pela comparação, porque a comparação é a primeira coisa que a rota faz.

A forma que falha é a checagem depois do trabalho. A rota faz a consulta, monta a resposta, e só então pergunta se devia ter feito. O efeito é observável: a linha foi apagada e a resposta foi 403. A interface não mostra nada, e o banco perdeu dado por causa de um botão que ninguém devia ver.

A diferença entre as duas formas é o que a equipe precisa escrever na revisão de código, e ela cabe em uma pergunta: qual é a primeira linha da função, depois de ler o pedido? Se a resposta não for a checagem, a rota está mal.

A regra que vale para esta aula inteira é checar no servidor, e vale para os dois lados do sistema de forma diferente. Do lado do navegador, a checagem serve para a experiência de quem usa: esconder o botão que não vai funcionar evita o erro. Do lado do servidor, a checagem serve para a segurança: recusar o pedido protege o dado. As duas são úteis, e só uma delas é obrigatória. O projeto do curso faz as duas, e a aula de hoje é sobre a segunda.

O problema prático de escrever a comparação dentro de cada rota é a rota esquecida. Não é esquecimento por distração: é que cada rota nova é um lugar novo onde a linha pode faltar, e o número de rotas cresce enquanto a atenção não. A rota que esquece a checagem é, quase sempre, a mais nova — a que alguém escreveu ontem à noite e testou com a própria conta de administrador.

O middleware — o termo em inglês; intermediate é a palavra que o professor usa quando alguém lê documentação antiga — é a função que roda antes da rota, para todas as rotas, e decide se o pedido chega. Com ele, a checagem centralizada é a regra: a rota declara o que exige e não repete a pergunta.

O desenho do middleware tem três partes, e a ordem delas importa:

  1. Ler quem é a pessoa a partir do token. Sem token, a resposta é 401 e a execução para aqui.
  2. Ler o que a pessoa pode, que é a consulta do dia 3 aula 1.
  3. Comparar com o que a rota exige, e recusar com 403 se faltar.

A parte 3 é a que evita a rota esquecida, e o motivo é estrutural: se a comparação está em um só lugar, o lugar novo é a declaração do requisito, e uma declaração faltando produz erro visível — a rota não entra na tabela, e o teste da tabela acusa. Com a comparação escrita em cada rota, o lugar novo é a implementação inteira, e uma implementação faltando produz silêncio.

O que o middleware não resolve é o caminho que não passa por ele. Uma rota registrada fora da cadeia, um endpoint de arquivo estático, um WebSocket aberto em outra porta: nada disso passa pelo middleware, e nada disso está protegido por ele. O professor pede que a turma procure esses caminhos no projeto, e o achado mais comum é o caminho de upload ou o caminho de webhook, que ninguém lembrava.

A tabela de rota por papel, e o teste de permissão que ela permite

A tabela de rota e papel — a lista de todas as rotas do sistema e do papel que cada uma exige. Ela não é código: é documento, e o professor trata como documento porque é o que é. O motivo de ela existir é transformar "esta rota está protegida?" de uma inspeção de código em uma comparação de tabela.

A tabela do projeto da turma fica assim, e o professor a preenche com eles:

RotaMétodoPapel que exigeAplica algo?
/api/leituraGETnenhum (autenticado)não
/api/historicoGETnenhum (autenticado)não
/api/configuracaoPUTeditorsim
/api/usuariosPOSTadministradorsim
/api/historicoDELETEadministradorsim
/api/estacao/reiniciarPOSTadministradorsim

A coluna da direita é a que o professor pede em negrito mental: aplica algo? A leitura pode ficar sem papel porque ler não muda o mundo; apagar muda. Uma tabela que só distingue "tem papel" e "não tem papel" erra a leitura, e erra mais do que erra a escrita — porque a rota que protege demais é mais barulhenta, e a que protege de menos só é descoberta por quem perdeu o dado.

Aplicar permissão é a operação de conferir o papel exigido pela tabela contra o que a pessoa tem, e a linha que a faz é a mesma para as seis rotas. É por isso que a tabela e o middleware andam juntos: a tabela é o que se consulta, e o middleware é quem consulta.

O ganho de manter os dois é o dia em que chegar o pedido de "e o professor, que pode ver tudo mas só mexer na própria estação?". Com a tabela, é uma linha nova com uma condição. Sem ela, são seis lugares para procurar a exceção, e a exceção escrita em seis lugares sempre diverge em um deles.

Teste de permissão: o caso que ninguém escreve

O teste de permissão é um teste comum, com um acréscimo: ele precisa de dois casos, e o segundo é o que o torna teste.

O caso negativo é o teste de que alguém sem permissão é recusado. Sem ele, o sistema pode estar completamente aberto e o teste continua verde, porque o único cenário testado é o do administrador entrando. Esse é o teste que pega a rota sem checagem, e ele é o que a turma vai escrever hoje.

O usuário sem permissão que o caso negativo usa precisa ser real, e não um token forjado. Há um detalhe técnico que o professor aponta aqui e que costuma passar batido: um token de teste com papel trocado à mão não serve, porque o servidor lê o papel da sessão, e a sessão com o papel trocado existe no banco de verdade. O teste de verdade cria a sessão, concede o papel errado, e só então chama a rota.

O par que a turma escreve para a rota de apagar histórico é:

  • positivo: sessão de administrador, DELETE, resposta 200, e a linha existe no banco.
  • negativo: sessão de leitor, DELETE, resposta 403, e a linha continua no banco.

A segunda parte do caso negativo é a que o professor chama de negativo do teste: não basta o status 403; a linha tem de continuar lá. Um 403 que apaga a linha e depois devolve erro é o pior resultado possível — o dado sumiu e o usuário recebeu a notícia de que ele não podia fazer nada. Esse é o mesmo "status HTTP errado" do dia 1, com dano real em cima.

O que o teste de permissão entrega além do 403 é o relatório de cobertura: quantas rotas foram testadas, quantas têm caso negativo, e quais estão sem caso nenhum. Esse relatório é o que a turma vai levar para o projeto final, e é mais útil do que qualquer tela de permissões.

Atividade

Montagem:

  • LED da placa no GPIO2, com o resistor de 330 ohm e o jumper para o GND.
  • Cabo USB conectado, monitor serial em 115200.
  • Servidor Node do 2o trimestre rodando, com as tabelas de usuário e de sessão criadas.
  • Nenhum sensor nesta aula: a prova é a resposta do servidor a duas chamadas, e um sensor só acrescentaria variação.
  1. Abra o seu projeto e liste todas as rotas que aplicam alguma coisa: mudam dado, criam registro ou apagam. Escreva arquivo, método e caminho de cada uma. Quantas rotas a lista tem?
  2. Preencha no papel a tabela de rota por papel com as rotas do item 1 e mais as duas rotas de leitura. Para cada rota, escreva qual papel ela exige. A coluna "aplica algo" da tabela da Conceitos é o que guia sua decisão.
  3. Para cada rota da sua tabela, escreva em qual linha do código está a checagem de permissão. Qual é a primeira linha da função, depois de ler o pedido? Marque na lista as rotas em que a checagem não é a primeira linha.
  4. Grave o sketch da resolução e rode por um minuto. Copie no caderno as duas chamadas que ele faz: a que devolve 200 e a que devolve 403, com o corpo da resposta de cada uma.
  5. Agora abra no navegador a rota protegida com um token de leitor, usando o curl no terminal em vez do painel. Escreva o status e o corpo da resposta que você recebeu. O corpo explica o motivo?
  6. Escreva o caso positivo e o caso negativo do teste de permissão para uma das rotas da sua tabela. Em cada um, escreva o que você vai verificar além do status. Quantas vezes a resposta 200 não basta?
  7. Escreva, no seu projeto, o middleware que centraliza a checagem, e registre as rotas que passam por ele. Depois procure caminhos que não passam por ele: arquivo estático, upload, webhook. Escreva na lista os nomes dos arquivos.
  8. Escolha uma rota que estava sem checagem e anote: o que essa rota faz, quem poderia fazê-lo indevidamente, e o que a turma teria perdido se ninguém tivesse achado.

Nota: 10 pontos. Critério de fim: a tabela de rota por papel preenchida com todas as rotas que aplicam algo, as duas respostas do sketch copiadas com 200 e 403, e uma rota sem checagem identificada com o número da linha.

Resolucao

O sketch da aula está em codigo/t3/dia03/aula2.ino.

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// Rede do laboratorio. Os dois valores sao FICTICIOS de proposito.
#define WIFI_SSID "lab-esp32-aula"
#define WIFI_PASSWORD "troque-esta-pela-sua"

#define URL_HISTORICO     "http://192.168.0.10:3000/api/historico"
#define URL_CONFIGURACAO  "http://192.168.0.10:3000/api/configuracao"
#define URL_APAGAR        "http://192.168.0.10:3000/api/historico"

const char* TOKEN_LEITOR      = "tk-leitor-bancada";     // FICTICIO
const char* TOKEN_ADMINISTRADOR = "tk-admin-bancada";    // FICTICIO

const int PIN_LED = 2;
const unsigned long INTERVALO_MS = 8000;

// ---------------------------------------------------------------------------
// A TABELA DE ROTA POR PAPEL, escrita na placa.
//
// Isto e uma COPIA do que o servidor tem. Ela existe para mostrar a estrutura
// do requisito, nao para ser a fonte da verdade: se alguem acrescentar uma rota
// no servidor e nao acrescentar aqui, a placa mostra uma tabela errada e o
// professor pergunta por que. A verdade continua no servidor.
// ---------------------------------------------------------------------------
struct Rota {
  const char* metodo;
  const char* caminho;
  const char* papelExigido;   // "" = so autenticado
  bool aplicaAlgo;
};

const Rota ROTAS[] = {
  {"GET",    "/api/leitura",     "",             false},
  {"GET",    "/api/historico",   "",             false},
  {"PUT",    "/api/configuracao", "editor",      true},
  {"POST",   "/api/usuarios",    "administrador",true},
  {"DELETE", "/api/historico",   "administrador",true}
};
const int TOTAL_ROTAS = 5;

// ---------------------------------------------------------------------------
// O QUE UMA ROTA PROTEGIDA FAZ, E POR QUE A RECUSA VEM ANTES DO TRABALHO.
//
// A funcao abaixo e o `middleware` escrito a mao. A ordem das tres etapas e o
// conteudo da aula: identificar, ter permissao, so entao executar. A 403 sai
// no terceiro passo, e o banco nao foi tocado ate la.
// ---------------------------------------------------------------------------

// Etapa 1: quem e a pessoa. Sem token, nao ha nem quem consultar.
int etapaIdentificar(const char* token) {
  if (token == nullptr || strlen(token) == 0) return 401;
  return 200;
}

// Etapa 2: o que a pessoa pode. No servidor isto e a consulta do dia 3 aula 1.
// Aqui devolvemos o papel, que e o que a tabela exige.
const char* etapaPermissao(const char* papel) {
  return papel;
}

// Etapa 3: a comparacao. A recusa acontece AQUI, antes de qualquer escrita.
int etapaChecar(const char* papelDaPessoa, const Rota& rota) {
  if (etapaIdentificar(TOKEN_LEITOR) == 401) return 401;
  const char* exigido = etapaPermissao(papelDaPessoa);
  if (rota.papelExigido[0] == '\0') return 200;    // so autenticado
  if (strcmp(exigido, rota.papelExigido) != 0) return 403;
  return 200;
}

// A rota protegida. A checagem e a PRIMEIRA coisa que ela faz: e o que garante
// que nao existe caminho que chegue ao trabalho sem passar pela comparacao.
int executarDelete(const char* papelDaPessoa) {
  const Rota& rota = ROTAS[4];
  int permitido = etapaChecar(papelDaPessoa, rota);
  if (permitido == 403) {
    Serial.println("    403 recusado pelo servidor. A linha nao foi apagada.");
    return 403;
  }
  Serial.println("    200 historico apagado. A linha saiu do banco.");
  return 200;
}

// O que a placa faz com a recusa. Ela NAO repete o pedido com outro token:
// 403 e "esta pessoa nao pode", e tentar de novo gasta bateria e nao muda nada.
void mostrarResultado(const char* rotulo, int status) {
  Serial.print("  ");
  Serial.print(rotulo);
  Serial.print(" -> HTTP ");
  Serial.println(status);
  if (status == 403) {
    Serial.println("      proibido: o painel esconde o botao, a rota recusa.");
  }
}

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  delay(200);

  Serial.println();
  Serial.println("=== dia 3, aula 2: checar a permissao em cada rota ===");
  Serial.println("O botao escondido e tela. A checagem e do servidor.");
  Serial.println();
  Serial.println("Tabela de rota por papel:");
  for (int i = 0; i < TOTAL_ROTAS; i++) {
    Serial.printf("  %-6s %-20s exige: %-14s aplica: %s\n",
                  ROTAS[i].metodo, ROTAS[i].caminho,
                  ROTAS[i].papelExigido[0] ? ROTAS[i].papelExigido : "(autenticado)",
                  ROTAS[i].aplicaAlgo ? "sim" : "nao");
  }
  Serial.println();
}

void loop() {
  digitalWrite(PIN_LED, HIGH);

  Serial.println("--- 1. rota que so exige estar autenticado ---");
  mostrarResultado("GET /api/leitura (leitor)", etapaChecar("leitor", ROTAS[0]));
  Serial.println();

  Serial.println("--- 2. rota que exige editor, chamada por um leitor ---");
  mostrarResultado("PUT /api/configuracao (leitor)", etapaChecar("leitor", ROTAS[2]));
  Serial.println("      o botao esta escondido no painel; a rota e que recusa.");
  Serial.println();

  Serial.println("--- 3. rota que exige editor, chamada por um editor ---");
  mostrarResultado("PUT /api/configuracao (editor)", etapaChecar("editor", ROTAS[2]));
  Serial.println();

  Serial.println("--- 4. a rota que apaga, com as duas pessoas ---");
  executarDelete("leitor");
  mostrarResultado("DELETE /api/historico (leitor)", 403);
  executarDelete("administrador");
  mostrarResultado("DELETE /api/historico (admin)", 200);
  Serial.println();

  digitalWrite(PIN_LED, LOW);
  delay(INTERVALO_MS);
}

Por que assim e não de outro jeito. A separação em etapaIdentificar, etapaPermissao e etapaChecar existe para que a ordem da aula fique visível no código. As três funções são o middleware escrito à mão, e cada uma tem uma resposta só. Se tudo estivesse em um if só, a turma leria um bloco de cinco linhas e não veria que são três decisões separadas — e é justamente a separação que impede a rota esquecida.

A ordem dentro de etapaChecar é o ponto mais importante do arquivo. A identificação vem antes porque não há o que comparar sem saber quem pergunta; uma comparação com pessoa desconhecida não tem autorização contra a qual comparar, e o sistema cairia no "permitir porque não achei quem não pode". A comparação do papel vem em seguida, e o caso do papel vazio — rota que exige só estar autenticado — resolve antes do strcmp, porque strcmp de ponteiro com ponteiro não diz nada sobre permissão.

O strcmp com a constante "leitor" hard-coded no sketch é a escolha mais deliberada do arquivo, e o professor a anuncia antes: este sketch não faz requisição de verdade. A aula precisa mostrar as duas respostas lado a lado em uma tela, e um servidor real devolveria a resposta em milissegundos, com uma placa dando voltas a cada oito segundos. O que o professor quer é a estrutura da decisão visível, e o Serial é o instrumento. O ROTAS também é cópia local, e o comentário do arquivo diz isso: a verdade é no servidor.

A duplicação entre etapaChecar e executarDelete é a que rende a discussão. A executarDelete chama a checagem e sai se ela recusar, e é essa saída antecipada que impede a escrita. A alternativa — chamar a checagem depois de apagar — produz o mesmo status 403 na tela e apaga a linha do banco. O professor pede que a turma veja as duas ordens e responda: o que muda para quem está lendo o log do banco?

A mostrarResultado trata 403 com uma frase que não é decoração, e ela é a mesma frase do dia 1: o painel esconde o botão, a rota recusa. A placa não repete o pedido, não tenta com outro token e não guarda nada para tentar depois. Ela avisa e para, e o próximo ciclo de oito segundos traz a mesma pergunta com a mesma resposta, que é o regime normal de um sistema sob autorização.

O LED acende e apaga a cada volta como sinal de vida, e o intervalo de oito segundos é o tempo de o professor ler a tela inteira sem precisar pausar. Como na aula anterior, é escolha de aula e não de projeto.

Criterios de correcao

CritérioPontos
Lista das rotas que aplicam algo, com arquivo, método e caminho2 pontos
Tabela de rota por papel preenchida, com as duas rotas de leitura incluídas2 pontos
A linha da checagem identificada em cada rota, com as marcadas fora de posição1 ponto
As duas respostas do sketch copiadas, com 200 e 403 e o corpo de cada uma2 pontos
A resposta obtida com o curl e token de leitor, com o status e o corpo1 ponto
O par de casos do teste de permissão escrito, com o que se verifica além do status1 ponto
Os caminhos que não passam pelo middleware, listados com o nome do arquivo1 ponto

Erros comuns

ErroComo apareceCorreção
Esconder o botão e chamar de protegidoO formulário não tem o campo e o teste passa"Botão escondido não protege: quem sabe onde ele estava pede a rota direto. A proteção mora na rota, e ela existe mesmo sem interface."
Checar a permissão depois de executarA rota apaga, monta a resposta e só então responde 403"A primeira linha depois de ler o pedido tem que ser a checagem. Se o 403 sai depois do trabalho, o status está certo e o dado já foi."
middleware aplicado a uma rota e esquecido em outraO DELETE protegido e o PUT de configuração aberto"Rota esquecida é a nova, quase sempre. Por isso a checagem precisa estar na cadeia e a exigência na tabela: faltar a declaração acusa, faltar a implementação silencia."
Testar só o caminho que entraUm teste verde com a conta de administrador"Teste de permissão tem caso positivo e caso negativo. Sem o negativo, você testou que a rota funciona, não que ela protege."
Confundir 401 com 403O painel manda a pessoa para o login quando o problema é permissão"401 é 'não sei quem você é'; 403 é 'sei, e você não pode'. Mandar para o login no 403 faz a pessoa logar de novo e ter o mesmo problema."
Usar o papel que vem no corpo do pedidoconst { papel } = req.body decide a autorização"O papel vem da sessão. Se o corpo do pedido escolhe o papel, qualquer cliente autenticado vira administrador com uma linha de texto."
403 depois de já ter apagadoO status diz proibido e a linha sumiu do banco"O caso negativo do teste verifica o banco, não só o status. Um 403 que apaga antes de recusar é o pior resultado: o dado sumiu e o usuário foi avisado de nada."
Tabela de rota escrita só para quem lêA tabela existe, e ninguém atualiza quando a rota muda"A tabela de rota por papel é documento vivo. Se ela diverge do código, ela vira mentira, e a pergunta 'esta rota está protegida' volta a exigir leitura de código."

Desafio extra

Escreva um teste automático que percorra a sua tabela de rota por papel e faça uma chamada com token de leitor para todas as rotas da coluna que exige algum papel. O teste deve reprovar se alguma responder diferente de 403, e deve imprimir o nome da rota que reprovou. Meça quanto tempo esse teste leva com seis rotas e com sessenta, usando a técnica de medir no próprio teste, e anote os dois números. Depois escreva a rota que apaga, com a checagem de permissão, o registro de quem pediu e a confirmação de que a linha continua no banco quando a resposta é 403. A pergunta que o professor espera de resposta: se um dia alguém acrescentar uma rota nova e esquecer a checagem, qual dos dois números que você mediu denuncia isso, e em quanto tempo?

>

A resolucao, compilada

// Aula 2 do dia 3 do 3o trimestre de ESP32 e IoT.
// Gerado a partir da secao ## Resolucao de conteudo/t3/dia03/aula2.md.
// Se mudar o codigo, mude no .md e regere: o .ino e copia do .md.

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

// Rede do laboratorio. Os dois valores sao FICTICIOS de proposito.
#define WIFI_SSID "lab-esp32-aula"
#define WIFI_PASSWORD "troque-esta-pela-sua"

#define URL_HISTORICO     "http://192.168.0.10:3000/api/historico"
#define URL_CONFIGURACAO  "http://192.168.0.10:3000/api/configuracao"
#define URL_APAGAR        "http://192.168.0.10:3000/api/historico"

const char* TOKEN_LEITOR      = "tk-leitor-bancada";     // FICTICIO
const char* TOKEN_ADMINISTRADOR = "tk-admin-bancada";    // FICTICIO

const int PIN_LED = 2;
const unsigned long INTERVALO_MS = 8000;

// ---------------------------------------------------------------------------
// A TABELA DE ROTA POR PAPEL, escrita na placa.
//
// Isto e uma COPIA do que o servidor tem. Ela existe para mostrar a estrutura
// do requisito, nao para ser a fonte da verdade: se alguem acrescentar uma rota
// no servidor e nao acrescentar aqui, a placa mostra uma tabela errada e o
// professor pergunta por que. A verdade continua no servidor.
// ---------------------------------------------------------------------------
struct Rota {
  const char* metodo;
  const char* caminho;
  const char* papelExigido;   // "" = so autenticado
  bool aplicaAlgo;
};

const Rota ROTAS[] = {
  {"GET",    "/api/leitura",     "",             false},
  {"GET",    "/api/historico",   "",             false},
  {"PUT",    "/api/configuracao", "editor",      true},
  {"POST",   "/api/usuarios",    "administrador",true},
  {"DELETE", "/api/historico",   "administrador",true}
};
const int TOTAL_ROTAS = 5;

// ---------------------------------------------------------------------------
// O QUE UMA ROTA PROTEGIDA FAZ, E POR QUE A RECUSA VEM ANTES DO TRABALHO.
//
// A funcao abaixo e o `middleware` escrito a mao. A ordem das tres etapas e o
// conteudo da aula: identificar, ter permissao, so entao executar. A 403 sai
// no terceiro passo, e o banco nao foi tocado ate la.
// ---------------------------------------------------------------------------

// Etapa 1: quem e a pessoa. Sem token, nao ha nem quem consultar.
int etapaIdentificar(const char* token) {
  if (token == nullptr || strlen(token) == 0) return 401;
  return 200;
}

// Etapa 2: o que a pessoa pode. No servidor isto e a consulta do dia 3 aula 1.
// Aqui devolvemos o papel, que e o que a tabela exige.
const char* etapaPermissao(const char* papel) {
  return papel;
}

// Etapa 3: a comparacao. A recusa acontece AQUI, antes de qualquer escrita.
int etapaChecar(const char* papelDaPessoa, const Rota& rota) {
  if (etapaIdentificar(TOKEN_LEITOR) == 401) return 401;
  const char* exigido = etapaPermissao(papelDaPessoa);
  if (rota.papelExigido[0] == '\0') return 200;    // so autenticado
  if (strcmp(exigido, rota.papelExigido) != 0) return 403;
  return 200;
}

// A rota protegida. A checagem e a PRIMEIRA coisa que ela faz: e o que garante
// que nao existe caminho que chegue ao trabalho sem passar pela comparacao.
int executarDelete(const char* papelDaPessoa) {
  const Rota& rota = ROTAS[4];
  int permitido = etapaChecar(papelDaPessoa, rota);
  if (permitido == 403) {
    Serial.println("    403 recusado pelo servidor. A linha nao foi apagada.");
    return 403;
  }
  Serial.println("    200 historico apagado. A linha saiu do banco.");
  return 200;
}

// O que a placa faz com a recusa. Ela NAO repete o pedido com outro token:
// 403 e "esta pessoa nao pode", e tentar de novo gasta bateria e nao muda nada.
void mostrarResultado(const char* rotulo, int status) {
  Serial.print("  ");
  Serial.print(rotulo);
  Serial.print(" -> HTTP ");
  Serial.println(status);
  if (status == 403) {
    Serial.println("      proibido: o painel esconde o botao, a rota recusa.");
  }
}

void setup() {
  pinMode(PIN_LED, OUTPUT);
  Serial.begin(115200);
  delay(200);

  Serial.println();
  Serial.println("=== dia 3, aula 2: checar a permissao em cada rota ===");
  Serial.println("O botao escondido e tela. A checagem e do servidor.");
  Serial.println();
  Serial.println("Tabela de rota por papel:");
  for (int i = 0; i < TOTAL_ROTAS; i++) {
    Serial.printf("  %-6s %-20s exige: %-14s aplica: %s\n",
                  ROTAS[i].metodo, ROTAS[i].caminho,
                  ROTAS[i].papelExigido[0] ? ROTAS[i].papelExigido : "(autenticado)",
                  ROTAS[i].aplicaAlgo ? "sim" : "nao");
  }
  Serial.println();
}

void loop() {
  digitalWrite(PIN_LED, HIGH);

  Serial.println("--- 1. rota que so exige estar autenticado ---");
  mostrarResultado("GET /api/leitura (leitor)", etapaChecar("leitor", ROTAS[0]));
  Serial.println();

  Serial.println("--- 2. rota que exige editor, chamada por um leitor ---");
  mostrarResultado("PUT /api/configuracao (leitor)", etapaChecar("leitor", ROTAS[2]));
  Serial.println("      o botao esta escondido no painel; a rota e que recusa.");
  Serial.println();

  Serial.println("--- 3. rota que exige editor, chamada por um editor ---");
  mostrarResultado("PUT /api/configuracao (editor)", etapaChecar("editor", ROTAS[2]));
  Serial.println();

  Serial.println("--- 4. a rota que apaga, com as duas pessoas ---");
  executarDelete("leitor");
  mostrarResultado("DELETE /api/historico (leitor)", 403);
  executarDelete("administrador");
  mostrarResultado("DELETE /api/historico (admin)", 200);
  Serial.println();

  digitalWrite(PIN_LED, LOW);
  delay(INTERVALO_MS);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 3 dia03 aula2.