diff --git a/docker-compose.yml b/docker-compose.yml index c6a225a..14ff93b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,12 +16,22 @@ services: volumes: - .:/usr/src/app environment: - - TESTING=1 + - TEMPLATES_AUTO_RELOAD=true - AUTHLIB_INSECURE_TRANSPORT=1 + + # Set these to your testing LDAP dn/password - LDAP_BIND_DN - LDAP_BIND_PASSWORD + # ...or uncomment this to allow any login with some mocked user info + # - TESTING=1 + - LOGGING_LEVEL=DEBUG + # Uncomment these to enable proper RSA JWT id_tokens signing + # - JWT_PRIVATE_KEY=private.pem + # - JWT_PUBLIC_KEYS=public.pem,public.pem + # - JWT_ALG=RS256 + volumes: pgdata: diff --git a/sso/oauth2.py b/sso/oauth2.py index aa07e1a..5a0935b 100644 --- a/sso/oauth2.py +++ b/sso/oauth2.py @@ -25,14 +25,6 @@ import logging log = logging.getLogger(__name__) -DUMMY_JWT_CONFIG = { - "key": "secret-key", - "alg": "HS256", - "iss": "https://sso.hackerspace.pl", - "exp": 3600, -} - - def exists_nonce(nonce, req): exists = AuthorizationCode.query.filter_by( client_id=req.client_id, nonce=nonce @@ -48,7 +40,7 @@ def generate_user_info(user, scope): preferred_username=user.username, nickname=user.username, groups=user.groups, - ) + ) def create_authorization_code(client, grant_user, request): @@ -116,7 +108,7 @@ class HybridGrant(_OpenIDHybridGrant): return exists_nonce(nonce, request) def get_jwt_config(self): - return DUMMY_JWT_CONFIG + return app.config.get("JWT_CONFIG") def generate_user_info(self, user, scope): return generate_user_info(user, scope) diff --git a/sso/settings.py b/sso/settings.py index 2e56f59..43a48cb 100644 --- a/sso/settings.py +++ b/sso/settings.py @@ -51,9 +51,24 @@ LDAP_BIND_PASSWORD = env.str("LDAP_BIND_PASSWORD", default="insert password here PROXYFIX_ENABLE = env.bool("PROXYFIX_ENABLE", default=True) PROXYFIX_NUM_PROXIES = env.int("PROXYFIX_NUM_PROXIES", default=1) +import pathlib +from authlib.jose import jwk + +jwt_alg = env.str("JWT_ALG", default="HS256") + +if jwt_alg == "HS256": + jwt_privkey = env.str("JWT_SECRET_KEY", default=SECRET_KEY) + JWT_PUBLIC_KEYS = [] +else: + jwt_privkey = jwk.dumps(env.path("JWT_PRIVATE_KEY").read_text(), kty="RSA") + JWT_PUBLIC_KEYS = [ + jwk.dumps(pathlib.Path(pub).read_text(), kty="RSA") + for pub in env.list("JWT_PUBLIC_KEYS") + ] + JWT_CONFIG = { - "key": env.str("JWT_SECRET_KEY", default=SECRET_KEY), - "alg": env.str("JWT_ALG", default="HS256"), + "key": jwt_privkey, + "alg": jwt_alg, "iss": env.str("JWT_ISS", default="https://sso.hackerspace.pl"), "exp": env.int("JWT_EXP", default=3600), } diff --git a/sso/views.py b/sso/views.py index 343f24e..08f8a7f 100644 --- a/sso/views.py +++ b/sso/views.py @@ -173,7 +173,6 @@ def authorize(): except OAuth2Error as error: return render_template("authorization_error.html", error=dict(error.get_body())) - print(grant) if grant.client.membership_required and not current_user.is_membership_active: return render_template("membership_required.html") @@ -239,6 +238,11 @@ def api_userinfo(): return jsonify(generate_user_info(user, current_token.scope)) +@bp.route("/.well-known/jwks.json") +def jwks_endpoint(): + return jsonify({"keys": current_app.config["JWT_PUBLIC_KEYS"]}) + + @bp.route("/.well-known/openid-configuration") def openid_configuration(): return jsonify( @@ -247,6 +251,7 @@ def openid_configuration(): "authorization_endpoint": url_for(".authorize", _external=True), "token_endpoint": url_for(".issue_token", _external=True), "userinfo_endpoint": url_for(".api_userinfo", _external=True), + "jwks_uri": url_for(".jwks_endpoint", _external=True), "response_types_supported": ["code", "id_token", "token id_token"], "token_endpoint_auth_methods_supported": [ "client_secret_basic",