This commit is contained in:
Alexis Bruneteau 2025-06-02 16:40:29 +02:00
parent 6019f0e1c0
commit d940ccced8
7 changed files with 368 additions and 0 deletions

18
apache/site.conf Normal file
View File

@ -0,0 +1,18 @@
<VirtualHost *:443>
ServerName app.local
SSLEngine on
SSLCertificateFile "/etc/apache2/ssl/cert.pem"
SSLCertificateKeyFile "/etc/apache2/ssl/key.pem"
ProxyPreserveHost On
# API Publique
ProxyPass /api/pub/ http://localhost:5001/
ProxyPassReverse /api/pub/ http://localhost:5001/
# API Utilisateur
ProxyPass /api/user/ http://localhost:5002/
ProxyPassReverse /api/user/ http://localhost:5002/
</VirtualHost>

51
docker-compose.yml Normal file
View File

@ -0,0 +1,51 @@
version: '3.8'
services:
keycloak-db:
image: postgres:15
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: password
volumes:
- keycloak-db:/var/lib/postgresql/data
keycloak:
image: quay.io/keycloak/keycloak:24.0.3
command: start-dev
environment:
KC_DB: postgres
KC_DB_URL_HOST: keycloak-db
KC_DB_URL_DATABASE: keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: password
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
ports:
- "8080:8080"
depends_on:
- keycloak-db
public_api:
build:
context: ./public
volumes:
- ./db.sqlite:/app/db.sqlite
ports:
- "5001:5001"
depends_on:
- keycloak
user_api:
build:
context: ./private
volumes:
- ./db.sqlite:/app/db.sqlite
ports:
- "5002:5002"
depends_on:
- keycloak
volumes:
keycloak-db:

151
keyclock-setup.sh Executable file
View File

@ -0,0 +1,151 @@
#!/bin/bash
# Variables
KC_HOST="http://localhost:8080"
REALM="myrealm"
CLIENT_ID="myclient"
CLIENT_SECRET="mysecret"
USERNAME="alexis"
PASSWORD="password"
# Fonction d'attente
wait_for_keycloak() {
echo "⏳ Attente de Keycloak..."
until curl -s "$KC_HOST" > /dev/null; do
sleep 2
done
echo "✅ Keycloak est prêt."
}
# Obtenir un token admin
get_admin_token() {
curl -s -X POST "$KC_HOST/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin" \
-d "password=admin" \
-d "grant_type=password" \
-d "client_id=admin-cli" |
jq -r .access_token
}
# Créer un realm, client et utilisateur
setup_keycloak() {
TOKEN=$(get_admin_token)
echo "🛠️ Création du realm $REALM..."
curl -s -X POST "$KC_HOST/admin/realms" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"realm\":\"$REALM\",\"enabled\":true}" > /dev/null
echo "🛠️ Création du client $CLIENT_ID..."
curl -s -X POST "$KC_HOST/admin/realms/$REALM/clients" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"clientId\": \"$CLIENT_ID\",
\"enabled\": true,
\"publicClient\": false,
\"secret\": \"$CLIENT_SECRET\",
\"redirectUris\": [\"*\"],
\"standardFlowEnabled\": true
}" > /dev/null
echo "👤 Création de l'utilisateur $USERNAME..."
curl -s -X POST "$KC_HOST/admin/realms/$REALM/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"username\": \"$USERNAME\",
\"enabled\": true,
\"credentials\": [{
\"type\": \"password\",
\"value\": \"$PASSWORD\",
\"temporary\": false
}]
}" > /dev/null
echo "✅ Configuration terminée !"
echo "🔐 Utilisateur: $USERNAME / $PASSWORD"
echo "🪪 Client secret: $CLIENT_SECRET"
}
# Lancer le setup
wait_for_keycloak
setup_keycloak
#!/bin/bash
# Variables
KC_HOST="http://localhost:8080"
REALM="myrealm"
CLIENT_ID="myclient"
CLIENT_SECRET="mysecret"
USERNAME="alexis"
PASSWORD="password"
# Fonction d'attente
wait_for_keycloak() {
echo "⏳ Attente de Keycloak..."
until curl -s "$KC_HOST" > /dev/null; do
sleep 2
done
echo "✅ Keycloak est prêt."
}
# Obtenir un token admin
get_admin_token() {
curl -s -X POST "$KC_HOST/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin" \
-d "password=admin" \
-d "grant_type=password" \
-d "client_id=admin-cli" |
jq -r .access_token
}
# Créer un realm, client et utilisateur
setup_keycloak() {
TOKEN=$(get_admin_token)
echo "🛠️ Création du realm $REALM..."
curl -s -X POST "$KC_HOST/admin/realms" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"realm\":\"$REALM\",\"enabled\":true}" > /dev/null
echo "🛠️ Création du client $CLIENT_ID..."
curl -s -X POST "$KC_HOST/admin/realms/$REALM/clients" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"clientId\": \"$CLIENT_ID\",
\"enabled\": true,
\"publicClient\": false,
\"secret\": \"$CLIENT_SECRET\",
\"redirectUris\": [\"*\"],
\"standardFlowEnabled\": true
}" > /dev/null
echo "👤 Création de l'utilisateur $USERNAME..."
curl -s -X POST "$KC_HOST/admin/realms/$REALM/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"username\": \"$USERNAME\",
\"enabled\": true,
\"credentials\": [{
\"type\": \"password\",
\"value\": \"$PASSWORD\",
\"temporary\": false
}]
}" > /dev/null
echo "✅ Configuration terminée !"
echo "🔐 Utilisateur: $USERNAME / $PASSWORD"
echo "🪪 Client secret: $CLIENT_SECRET"
}
# Lancer le setup
wait_for_keycloak
setup_keycloak

9
private/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install flask flask_sqlalchemy pyjwt requests
CMD ["python", "app.py"]

83
private/app.py Normal file
View File

@ -0,0 +1,83 @@
--- user_api/app.py ---
from flask import Flask, jsonify, request, abort
from flask_sqlalchemy import SQLAlchemy
import requests
import jwt
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../db.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Configuration Keycloak
KEYCLOAK_URL = "http://localhost:8080"
REALM = "myrealm"
CLIENT_ID = "myclient"
class Visite(db.Model):
id = db.Column(db.Integer, primary_key=True)
galerie_id = db.Column(db.Integer, nullable=False)
class Critique(db.Model):
id = db.Column(db.Integer, primary_key=True)
oeuvre_id = db.Column(db.Integer, nullable=False)
texte = db.Column(db.Text, nullable=False)
username = db.Column(db.String(100), nullable=False)
def verify_token(auth_header):
if not auth_header.startswith("Bearer "):
return None
token = auth_header.split(" ")[1]
try:
jwks_url = f"{KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/certs"
jwks = requests.get(jwks_url).json()
public_keys = {k['kid']: jwt.algorithms.RSAAlgorithm.from_jwk(k) for k in jwks['keys']}
unverified = jwt.decode(token, options={"verify_signature": False})
kid = unverified['kid']
decoded = jwt.decode(token, key=public_keys[kid], algorithms=['RS256'], audience=CLIENT_ID)
return decoded['preferred_username']
except Exception as e:
print(f"JWT verification failed: {e}")
return None
@app.before_request
def authenticate():
auth = request.headers.get("Authorization", "")
user = verify_token(auth)
if not user:
return jsonify({"error": "Unauthorized"}), 403
request.user = user
@app.route("/", methods=["GET"])
def index():
return f"User API - Authenticated as {request.user}", 200
@app.route("/galerie/<int:galerie_id>/entrer", methods=["POST"])
def entrer_galerie(galerie_id):
visite = Visite(galerie_id=galerie_id)
db.session.add(visite)
db.session.commit()
return jsonify({"message": "Entré dans la galerie"}), 201
@app.route("/galerie/<int:galerie_id>/sortir", methods=["POST"])
def sortir_galerie(galerie_id):
Visite.query.filter_by(galerie_id=galerie_id).delete()
db.session.commit()
return jsonify({"message": "Sorti de la galerie"}), 200
@app.route("/oeuvre/<int:oeuvre_id>/critiquer", methods=["POST"])
def critiquer_oeuvre(oeuvre_id):
data = request.get_json()
if not data or not data.get("texte"):
abort(400)
critique = Critique(oeuvre_id=oeuvre_id, texte=data["texte"], username=request.user)
db.session.add(critique)
db.session.commit()
return jsonify({"message": "Critique ajoutée"}), 201
if __name__ == "__main__":
with app.app_context():
db.create_all()
app.run(port=5002)

9
public/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install flask flask_sqlalchemy pyjwt requests
CMD ["python", "app.py"]

47
public/app.py Normal file
View File

@ -0,0 +1,47 @@
--- public_api/app.py ---
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../db.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Artiste(db.Model):
id = db.Column(db.Integer, primary_key=True)
nom = db.Column(db.String(100), nullable=False)
class Galerie(db.Model):
id = db.Column(db.Integer, primary_key=True)
nom = db.Column(db.String(100), nullable=False)
class Oeuvre(db.Model):
id = db.Column(db.Integer, primary_key=True)
titre = db.Column(db.String(200), nullable=False)
exposee = db.Column(db.Boolean, default=False)
@app.route("/", methods=["GET"])
def index():
return "Public API", 200
@app.route("/artistes", methods=["GET"])
def get_artistes():
artistes = Artiste.query.all()
return jsonify([{"id": a.id, "nom": a.nom} for a in artistes]), 200
@app.route("/galeries", methods=["GET"])
def get_galeries():
galeries = Galerie.query.all()
return jsonify([{"id": g.id, "nom": g.nom} for g in galeries]), 200
@app.route("/oeuvres", methods=["GET"])
def get_oeuvres():
oeuvres = Oeuvre.query.filter_by(exposee=True).all()
return jsonify([{"id": o.id, "titre": o.titre} for o in oeuvres]), 200
if __name__ == "__main__":
with app.app_context():
db.create_all()
app.run(port=5001)