init
This commit is contained in:
parent
6019f0e1c0
commit
d940ccced8
18
apache/site.conf
Normal file
18
apache/site.conf
Normal 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
51
docker-compose.yml
Normal 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
151
keyclock-setup.sh
Executable 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
9
private/Dockerfile
Normal 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
83
private/app.py
Normal 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
9
public/Dockerfile
Normal 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
47
public/app.py
Normal 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)
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user