import os from flask import Flask, redirect, url_for, session, request, jsonify, render_template, abort from authlib.integrations.flask_client import OAuth from werkzeug.middleware.proxy_fix import ProxyFix import builds app = Flask(__name__) app.secret_key = os.environ["SECRET_KEY"] # Gère X-Forwarded-Proto envoyé par Apache app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) # ── OIDC AlpID ──────────────────────────────────────────────────── oauth = OAuth(app) oauth.register( name="alpid", server_metadata_url=os.environ["ALPID_DISCOVERY_URL"], client_id=os.environ["ALPID_CLIENT_ID"], client_secret=os.environ["ALPID_CLIENT_SECRET"], client_kwargs={"scope": "openid profile email groups"}, ) ADMIN_GROUPS = set(os.environ.get("ADMIN_GROUPS", "admins").split(",")) def _user(): return session.get("user") def _require_admin(): user = _user() if not user: session["next_url"] = request.url return redirect(url_for("login")) if not user.get("is_admin"): abort(403) return None # ── Auth ────────────────────────────────────────────────────────── @app.route("/auth/login") def login(): return oauth.alpid.authorize_redirect(url_for("callback", _external=True)) @app.route("/auth/callback") def callback(): token = oauth.alpid.authorize_access_token() info = token.get("userinfo") or oauth.alpid.userinfo(token=token) # Keycloak expose les groupes dans le claim "groups" (à activer dans le mapper) groups = set(info.get("groups", [])) is_admin = bool(groups & ADMIN_GROUPS) session["user"] = { "sub": info["sub"], "name": info.get("name") or info.get("preferred_username", "Admin"), "email": info.get("email", ""), "is_admin": is_admin, } return redirect(session.pop("next_url", url_for("index"))) @app.route("/auth/logout") def logout(): session.clear() return redirect(url_for("index")) # ── Pages ───────────────────────────────────────────────────────── @app.route("/") def index(): redir = _require_admin() if redir: return redir state = builds.get_state() log = builds.get_log() return render_template("index.html", user=_user(), state=state, log=log) # ── API JSON ────────────────────────────────────────────────────── @app.route("/api/build", methods=["POST"]) def api_build(): redir = _require_admin() if redir: return jsonify({"error": "non authentifié"}), 401 user = _user() ok = builds.start_build(triggered_by=user["name"]) if ok: return jsonify({"started": True}) return jsonify({"started": False, "reason": "Un build est déjà en cours"}), 409 @app.route("/api/status") def api_status(): redir = _require_admin() if redir: return jsonify({"error": "non authentifié"}), 401 state = builds.get_state() return jsonify({ "running": state.get("running", False), "last_success": state.get("last_success"), "last_duration": state.get("last_duration_s"), "started_at": state.get("started_at"), "triggered_by": state.get("triggered_by"), "history": state.get("history", [])[:5], }) @app.route("/api/log") def api_log(): redir = _require_admin() if redir: return jsonify({"error": "non authentifié"}), 401 return jsonify({"log": builds.get_log()}) if __name__ == "__main__": app.run(debug=False, port=5002)