Ficha 02 · API REST, BD e autenticação
- Desenhar API REST
- Usar ORM
- Implementar autenticação
- Aplicar segurança
Parte I · API REST
Exercício 1 · Desenhar endpoints (15 pts)
Para um recurso "tarefa", desenha os endpoints REST (método + rota + código de sucesso):
a) Listar todas. ___
b) Obter uma. ___
c) Criar. ___
d) Actualizar. ___
e) Apagar. ___
| Operação | Método + rota | Código |
|---|---|---|
| Listar | GET /api/tarefas |
200 |
| Obter uma | GET /api/tarefas/<id> |
200 / 404 |
| Criar | POST /api/tarefas |
201 |
| Actualizar | PUT /api/tarefas/<id> (ou PATCH) |
200 / 404 |
| Apagar | DELETE /api/tarefas/<id> |
204 / 404 |
Recursos no plural, sem verbos na URL.
Exercício 2 · Implementar API (20 pts)
Implementa em Flask os endpoints GET (listar), GET (um), POST (criar) para "tarefa" (id, titulo, feita). Trata 404 e validação.
from flask import Flask, jsonify, request
app = Flask(__name__)
tarefas = []
proximo_id = 1
@app.route("/api/tarefas", methods=["GET"])
def listar():
return jsonify(tarefas)
@app.route("/api/tarefas/<int:id>", methods=["GET"])
def obter(id):
t = next((x for x in tarefas if x["id"] == id), None)
if t is None:
return jsonify({"erro": "Não encontrada"}), 404
return jsonify(t)
@app.route("/api/tarefas", methods=["POST"])
def criar():
global proximo_id
dados = request.get_json()
if not dados or not dados.get("titulo"):
return jsonify({"erro": "titulo obrigatório"}), 400
t = {"id": proximo_id, "titulo": dados["titulo"], "feita": False}
tarefas.append(t)
proximo_id += 1
return jsonify(t), 201
Exercício 3 · Códigos de estado (10 pts)
Que código HTTP devolver em cada situação?
a) Recurso criado com sucesso. ___
b) Recurso apagado com sucesso (sem conteúdo a devolver). ___
c) Pedido com JSON inválido. ___
d) Recurso não existe. ___
e) Utilizador não autenticado. ___
a) 201 Created. b) 204 No Content. c) 400 Bad Request. d) 404 Not Found. e) 401 Unauthorized (ou 403 Forbidden se autenticado mas sem permissão).
Parte II · Base de dados
Exercício 4 · Modelo + CRUD (20 pts)
Define um modelo Flask-SQLAlchemy Tarefa(id, titulo, feita) e escreve as 4 operações CRUD.
from flask_sqlalchemy import SQLAlchemy
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
db = SQLAlchemy(app)
class Tarefa(db.Model):
id = db.Column(db.Integer, primary_key=True)
titulo = db.Column(db.String(200), nullable=False)
feita = db.Column(db.Boolean, default=False)
with app.app_context():
db.create_all()
# CREATE
t = Tarefa(titulo="Estudar")
db.session.add(t)
db.session.commit()
# READ
todas = Tarefa.query.all()
uma = Tarefa.query.get(1)
pendentes = Tarefa.query.filter_by(feita=False).all()
# UPDATE
t = Tarefa.query.get(1)
t.feita = True
db.session.commit()
# DELETE
db.session.delete(t)
db.session.commit()
Exercício 5 · Rota com BD (10 pts)
Escreve uma rota POST /tarefas que cria uma tarefa na BD a partir de um formulário e redirecciona para a lista.
from flask import request, redirect, url_for
@app.route("/tarefas", methods=["POST"])
def criar_tarefa():
titulo = request.form.get("titulo", "").strip()
if not titulo:
flash("Título obrigatório")
return redirect(url_for("listar_tarefas"))
db.session.add(Tarefa(titulo=titulo))
db.session.commit()
return redirect(url_for("listar_tarefas"))
@app.route("/tarefas")
def listar_tarefas():
tarefas = Tarefa.query.order_by(Tarefa.id.desc()).all()
return render_template("tarefas.html", tarefas=tarefas)
Parte III · Autenticação
Exercício 6 · Hash de senha (15 pts)
a) Porque não se guarda a senha em texto plano? (5 pts)
b) Escreve código de registo (hash) e login (verificar) com werkzeug. (10 pts)
a) Se a BD for comprometida (fuga de dados), senhas em texto plano expõem todas as contas — e como utilizadores reutilizam senhas, expõem contas noutros serviços. Hash (com salt) torna inviável recuperar a senha original.
b)
from werkzeug.security import generate_password_hash, check_password_hash
# Registo
def registar(email, senha):
if len(senha) < 10:
raise ValueError("Senha demasiado curta")
user = User(
email=email,
pwd_hash=generate_password_hash(senha)
)
db.session.add(user)
db.session.commit()
# Login
def autenticar(email, senha):
user = User.query.filter_by(email=email).first()
if user and check_password_hash(user.pwd_hash, senha):
return user
return None
generate_password_hash aplica salt automaticamente e usa algoritmo lento (scrypt/pbkdf2).
Exercício 7 · Proteger rota (10 pts)
Com Flask-Login, escreve uma rota /painel acessível só a utilizadores autenticados, mostrando o email do utilizador actual.
from flask_login import login_required, current_user
@app.route("/painel")
@login_required
def painel():
return render_template("painel.html", utilizador=current_user)
<!-- painel.html -->
{% extends "base.html" %}
{% block conteudo %}
<h1>Olá, {{ utilizador.email }}</h1>
{% endblock %}
@login_required redirecciona automaticamente para o login se não autenticado. current_user é o utilizador da sessão. A verificação é no servidor — esconder o link no frontend não basta.