Autorizacao e permissoes — Arduino e IoT — semana 3 do 3o trimestre
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
DHT11por dupla, com resistor de pull-up de 4,7 k ohm entre o pino de dados e3V3 - 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.
| Conceito | Pergunta que responde | Onde fica guardado |
|---|---|---|
| autenticação | quem é você | a sessão, com o identificador |
| permissão | o que esta pessoa pode | a tabela de permissões |
| papel | o conjunto de permissões | a tabela de papéis |
| role | o papel, em inglês | a 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ão | leitor | editor | administrador |
|---|---|---|---|
| ver leitura atual | sim | sim | sim |
| ver histórico | sim | sim | sim |
| mudar configuração da estação | não | sim | sim |
| cadastrar usuário | não | não | sim |
| apagar histórico | não | não | sim |
| revogar permissão | não | não | sim |
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:
DHT11noGPIO4, com pull-up de 4,7 k ohm entre o pino de dados e3V3.LEDda placa noGPIO2, com o resistor de 330 ohm e o jumper para oGND.- Cabo USB conectado, monitor serial em 115200.
- Servidor Node do 2o trimestre rodando, com as tabelas de usuário e de sessão criadas.
- 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.
- 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.
- 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.
- 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.
- No seu projeto, troque o papel da sua sessão para
editore 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? - 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. - 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.
- 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ério | Pontos |
|---|---|
| Matriz de três papéis por seis permissões preenchida, com a regra escrita | 2 pontos |
| O lugar do papel no projeto identificado, com arquivo, linha e o formato | 1 ponto |
| As três consultas do sketch copiadas, com o resultado de cada uma | 2 pontos |
| Item 4: quantas consultas foram ao servidor e quantas ficaram na placa | 1 ponto |
Item 5: o que mudou no painel ao trocar para editor, com o esperado e o obtido | 1 ponto |
| A resposta do servidor à requisição com o papel editado, e a linha do código que decide | 2 pontos |
| A lista das rotas que mudam algo, com arquivo e linha de cada uma | 1 ponto |
Erros comuns
| Erro | Como aparece | Correção |
|---|---|---|
| Papel em coluna de texto com lista separada por vírgula | papeis = '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 pedido | const { 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ão | O 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 banco | O 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ção | O 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 403 | O 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ões | A 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 interface | A 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
middlewareque 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:
- Ler quem é a pessoa a partir do token. Sem token, a resposta é 401 e a execução para aqui.
- Ler o que a pessoa pode, que é a consulta do dia 3 aula 1.
- 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:
| Rota | Método | Papel que exige | Aplica algo? |
|---|---|---|---|
/api/leitura | GET | nenhum (autenticado) | não |
/api/historico | GET | nenhum (autenticado) | não |
/api/configuracao | PUT | editor | sim |
/api/usuarios | POST | administrador | sim |
/api/historico | DELETE | administrador | sim |
/api/estacao/reiniciar | POST | administrador | sim |
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:
LEDda placa noGPIO2, com o resistor de 330 ohm e o jumper para oGND.- 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.
- 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?
- 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.
- 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.
- 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.
- Agora abra no navegador a rota protegida com um token de leitor, usando o
curlno terminal em vez do painel. Escreva o status e o corpo da resposta que você recebeu. O corpo explica o motivo? - 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?
- Escreva, no seu projeto, o
middlewareque 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. - 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ério | Pontos |
|---|---|
| Lista das rotas que aplicam algo, com arquivo, método e caminho | 2 pontos |
| Tabela de rota por papel preenchida, com as duas rotas de leitura incluídas | 2 pontos |
| A linha da checagem identificada em cada rota, com as marcadas fora de posição | 1 ponto |
| As duas respostas do sketch copiadas, com 200 e 403 e o corpo de cada uma | 2 pontos |
A resposta obtida com o curl e token de leitor, com o status e o corpo | 1 ponto |
| O par de casos do teste de permissão escrito, com o que se verifica além do status | 1 ponto |
Os caminhos que não passam pelo middleware, listados com o nome do arquivo | 1 ponto |
Erros comuns
| Erro | Como aparece | Correção |
|---|---|---|
| Esconder o botão e chamar de protegido | O 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 executar | A 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 outra | O 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 entra | Um 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 403 | O 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 pedido | const { 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 apagado | O 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.
