From 97a39eadf858c9d4ed86fa6b10201a15e00b16ec Mon Sep 17 00:00:00 2001 From: Franziska Kunsmann Date: Tue, 24 Dec 2024 12:12:46 +0100 Subject: [PATCH] fix c3hub sso integration --- frontend.py | 57 ++++++++++++++++++++++++++++---------------- redis_session.py | 2 +- util/__init__.py | 4 ++-- util/sso/__init__.py | 5 +++- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/frontend.py b/frontend.py index bb34fc4..f8eabe9 100644 --- a/frontend.py +++ b/frontend.py @@ -1,7 +1,9 @@ import random import socket +from base64 import urlsafe_b64encode from collections import defaultdict from datetime import datetime, timezone +from hashlib import sha256 from secrets import token_hex from typing import Iterable from urllib.parse import urlencode @@ -176,18 +178,27 @@ def login(provider): session["oauth2_state"] = state = get_random() - qs = urlencode( - { - "client_id": provider_config["client_id"], - "redirect_uri": url_for( - "oauth2_callback", provider=provider, _external=True - ), - "response_type": "code", - "scope": " ".join(SSO_CONFIG[provider]["scopes"]), - "state": state, - } + params = { + "client_id": provider_config["client_id"], + "redirect_uri": url_for("oauth2_callback", provider=provider, _external=True), + "response_type": "code", + "scope": " ".join(SSO_CONFIG[provider]["scopes"]), + } + extra_args = "" + + if SSO_CONFIG[provider]["challenge_instead_of_state"]: + challenge = sha256(state.encode("utf-8")).digest() + challenge = urlsafe_b64encode(challenge).decode("utf-8").replace("=", "") + extra_args = f"&code_challenge={challenge}" + params["code_challenge_method"] = "S256" + else: + params["state"] = state + + return redirect( + "{}?{}{}".format( + SSO_CONFIG[provider]["authorize_url"], urlencode(params), extra_args + ) ) - return redirect("{}?{}".format(SSO_CONFIG[provider]["authorize_url"], qs)) @app.route("/login/callback/") @@ -205,23 +216,27 @@ def oauth2_callback(provider): flash(f"{k}: {v}", "danger") return redirect(url_for("index")) - if request.args["state"] != session.get("oauth2_state"): + if not SSO_CONFIG[provider]["challenge_instead_of_state"] and request.args[ + "state" + ] != session.get("oauth2_state"): abort(401) if "code" not in request.args: abort(400) + params = { + "client_id": provider_config["client_id"], + "client_secret": provider_config["client_secret"], + "code": request.args["code"], + "grant_type": "authorization_code", + "redirect_uri": url_for("oauth2_callback", provider=provider, _external=True), + } + if SSO_CONFIG[provider]["challenge_instead_of_state"]: + params["code_verifier"] = session["oauth2_state"] + r = requests.post( SSO_CONFIG[provider]["token_url"], - data={ - "client_id": provider_config["client_id"], - "client_secret": provider_config["client_secret"], - "code": request.args["code"], - "grant_type": "authorization_code", - "redirect_uri": url_for( - "oauth2_callback", provider=provider, _external=True - ), - }, + data=params, headers={"Accept": "application/json"}, ) if r.status_code != 200: diff --git a/redis_session.py b/redis_session.py index f2f174e..4c38e05 100644 --- a/redis_session.py +++ b/redis_session.py @@ -14,7 +14,7 @@ def on_update(self): sessions.CallbackDict.__init__(self, initial, on_update) self.modified = False self.new_sid = not sid - self.sid = sid or get_random(32) + self.sid = sid or get_random() class RedisSessionStore(sessions.SessionInterface): diff --git a/util/__init__.py b/util/__init__.py index 6e719ac..a97c10c 100644 --- a/util/__init__.py +++ b/util/__init__.py @@ -165,8 +165,8 @@ def is_within_timeframe(): return CONFIG["TIME_MIN"] < now < CONFIG["TIME_MAX"] -def get_random(size=16): - return "".join("%02x" % random.getrandbits(8) for i in range(size)) +def get_random(): + return "".join("%02x" % random.getrandbits(8) for i in range(64)) def cached_asset_name(asset: Asset): diff --git a/util/sso/__init__.py b/util/sso/__init__.py index 3c7a085..3eb749d 100644 --- a/util/sso/__init__.py +++ b/util/sso/__init__.py @@ -20,8 +20,9 @@ "display_name": "38C3 Hub", "authorize_url": "https://events.ccc.de/congress/2024/hub/sso/authorize", "token_url": "https://events.ccc.de/congress/2024/hub/sso/token", - "scopes": [], + "scopes": ["38c3_attendee"], "userinfo_url": "https://api.events.ccc.de/congress/2024/me", + "challenge_instead_of_state": True, "functions": { "is_admin": lambda json: False, "login_allowed": lambda json: True, @@ -36,6 +37,7 @@ "token_url": "https://sso.c3voc.de/application/o/token/", "scopes": ["openid", "profile", "groups"], "userinfo_url": "https://sso.c3voc.de/application/o/userinfo/", + "challenge_instead_of_state": False, "functions": { "is_admin": check_c3voc_is_admin, "login_allowed": check_c3voc_allowed_login, @@ -50,6 +52,7 @@ "token_url": "https://github.com/login/oauth/access_token", "scopes": [], "userinfo_url": "https://api.github.com/user", + "challenge_instead_of_state": False, "functions": { "is_admin": check_github_is_admin, "login_allowed": check_github_allowed_login,