From f798e674ce7d1047db48d5528382205fec7725d8 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Wed, 5 Aug 2026 17:17:25 +0200 Subject: [PATCH 1/4] feat: log in with OAuth 2.0 / OIDC This allows using 'official' OAuth2/OIDC rather than the ASF dialect, and enables more detailed logging from mfa.apache.org requires https://github.com/apache/infrastructure-asfquart/pull/107 --- CONTRIBUTING.md | 10 ++++++++++ app/__init__.py | 16 ++++++++++++++++ app/config.py | 15 +++++++++++++++ config.yaml.example | 19 +++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ce93893 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ +To run the dashboard locally: + +- Copy `config.yaml.example` to `config.yaml` +- run `python3 server.py` + +# Authorization + +There is an example config for testing against https://mfa-dev.apache.org . + +If you want to test with different authorizations than your own, you can either patch the code accordingly or start a local oauth server (e.g. [mock-oauth2-server](https://github.com/navikt/mock-oauth2-server), `JSON_CONFIG_PATH=./mock-oauth2-server-config.json nix run git+https://codeberg.org/raboof/mock-oauth2-server?ref=nix --no-write-lock-file`) diff --git a/app/__init__.py b/app/__init__.py index c4e13b9..9ec1114 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -213,6 +213,20 @@ async def add_security_headers(response: quart.Response) -> quart.Response: response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin") return response +def _configure_oauth_server(app_config: AppConfig) -> None: + """Point asfquart's OAuth flow at the configured OIDC server.""" + import asfquart.generics + asfquart.generics.OAUTH_URL_INIT = ( + f"{app_config.oauth_url_init}?state=%s&redirect_uri=%s" + ) + asfquart.generics.OAUTH_URL_CALLBACK = app_config.oauth_url_callback + asfquart.generics.OAUTH_ENFORCE_HTTPS = app_config.oauth_enforce_https + asfquart.generics.OAUTH_CLIENT_ID = app_config.oauth_client_id + asfquart.generics.OAUTH_CLIENT_SECRET = app_config.oauth_client_secret + asfquart.generics.OAUTH_URL_JWKS = app_config.oauth_url_jwks + asfquart.generics.OAUTH_URL_LOGOUT = app_config.oauth_url_logout + asfquart.generics.OAUTH_ISSUER = app_config.oauth_issuer + def create_app(test_environment: bool = False) -> asfquart.base.QuartApp: from app import config app_dir = None @@ -233,6 +247,8 @@ def create_app(test_environment: bool = False) -> asfquart.base.QuartApp: quart_app = asfquart.construct("security-dashboard", app_dir, cfg_file, token_file) + _configure_oauth_server(app_config) + config.setup_app_config(quart_app, app_config) _register_routes(quart_app) diff --git a/app/config.py b/app/config.py index b620d51..458eb99 100644 --- a/app/config.py +++ b/app/config.py @@ -56,6 +56,21 @@ class AppConfig(pydantic.BaseModel): pmcs_in_attic: list[str] = [] """PMCs retired to the Attic, whose reports the security team handles directly""" + oauth_url_init: str = "https://oauth.apache.org/auth-oidc" + """URL of the ASF OIDC/OAuth server.""" + + oauth_url_callback: str = "https://oauth.apache.org/token-oidc" + """URL of the ASF OIDC/OAuth server token endpoint.""" + + oauth_url_jwks: str = None; + oauth_url_logout: str = None; + oauth_client_id: str = None; + oauth_client_secret: str = None; + oauth_issuer: str = None; + + oauth_enforce_https: bool = True + """enforce HTTPS in the callback to the relying party.""" + server: ServerConfig = ServerConfig() @property diff --git a/config.yaml.example b/config.yaml.example index 85b60bd..10b0d91 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -106,3 +106,22 @@ pmcs_in_attic: - wink - wookie - xmlbeans + +# for local testing with mock-oauth2-server +#oauth_url_init: http://localhost:8080/default/authorize +#oauth_client_id: example-app +#oauth_client_secret: ZXhhbXBsZS1hcHAtc2VjcmV0 +#oauth_url_callback: http://localhost:8080/default/token +#oauth_url_jwks: http://localhost:8080/default/jwks +#oauth_issuer: http://localhost:8080/default +#oauth_enforce_https: false + +# for local testing with https://mfa-dev.apache.org +oauth_url_init: https://mfa-dev.apache.org/application/o/authorize/ +oauth_client_id: local_testing.apache.org +oauth_client_secret: (ask) +oauth_url_callback: https://mfa-dev.apache.org/application/o/token/ +oauth_url_jwks: https://mfa-dev.apache.org/application/o/local_testing-apache-org/jwks/ +oauth_url_logout: https://mfa-dev.apache.org/application/o/local_testing-apache-org/end-session/ +oauth_issuer: https://mfa-dev.apache.org/application/o/local_testing-apache-org/ +oauth_enforce_https: false From 3f71b7e2a97549deb8a5ad2aa91920ed8b58ccd5 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Thu, 27 Aug 2026 17:03:13 +0200 Subject: [PATCH 2/4] remove jwks this does not need to be specified, it's taken from the published oidc config --- app/__init__.py | 1 - app/config.py | 1 - config.yaml.example | 2 -- 3 files changed, 4 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 9ec1114..f814f12 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -223,7 +223,6 @@ def _configure_oauth_server(app_config: AppConfig) -> None: asfquart.generics.OAUTH_ENFORCE_HTTPS = app_config.oauth_enforce_https asfquart.generics.OAUTH_CLIENT_ID = app_config.oauth_client_id asfquart.generics.OAUTH_CLIENT_SECRET = app_config.oauth_client_secret - asfquart.generics.OAUTH_URL_JWKS = app_config.oauth_url_jwks asfquart.generics.OAUTH_URL_LOGOUT = app_config.oauth_url_logout asfquart.generics.OAUTH_ISSUER = app_config.oauth_issuer diff --git a/app/config.py b/app/config.py index 458eb99..3289ee1 100644 --- a/app/config.py +++ b/app/config.py @@ -62,7 +62,6 @@ class AppConfig(pydantic.BaseModel): oauth_url_callback: str = "https://oauth.apache.org/token-oidc" """URL of the ASF OIDC/OAuth server token endpoint.""" - oauth_url_jwks: str = None; oauth_url_logout: str = None; oauth_client_id: str = None; oauth_client_secret: str = None; diff --git a/config.yaml.example b/config.yaml.example index 10b0d91..66fe215 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -112,7 +112,6 @@ pmcs_in_attic: #oauth_client_id: example-app #oauth_client_secret: ZXhhbXBsZS1hcHAtc2VjcmV0 #oauth_url_callback: http://localhost:8080/default/token -#oauth_url_jwks: http://localhost:8080/default/jwks #oauth_issuer: http://localhost:8080/default #oauth_enforce_https: false @@ -121,7 +120,6 @@ oauth_url_init: https://mfa-dev.apache.org/application/o/authorize/ oauth_client_id: local_testing.apache.org oauth_client_secret: (ask) oauth_url_callback: https://mfa-dev.apache.org/application/o/token/ -oauth_url_jwks: https://mfa-dev.apache.org/application/o/local_testing-apache-org/jwks/ oauth_url_logout: https://mfa-dev.apache.org/application/o/local_testing-apache-org/end-session/ oauth_issuer: https://mfa-dev.apache.org/application/o/local_testing-apache-org/ oauth_enforce_https: false From bd9060f9ea498ed25b152e856618914e1fbe0650 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Fri, 4 Sep 2026 10:32:23 +0200 Subject: [PATCH 3/4] use asfquart from PR --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a866530..0f3c041 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,8 @@ license = "Apache-2.0" dependencies = [ "asfpy[aioldap]", - "asfquart~=0.1.12", + #"asfquart~=0.1.12", + "asfquart @ git+https://github.com/raboof/infrastructure-asfquart@oauth-support-client-id-and-secret", "hypercorn", "pydantic~=2.11", ] From 2bc2bfbfb219e12f8d2a5275caf804169273a4e4 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Fri, 4 Sep 2026 10:33:00 +0200 Subject: [PATCH 4/4] specify asfquart branch in pyproject.py --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0f3c041..d1eb382 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ license = "Apache-2.0" dependencies = [ "asfpy[aioldap]", - #"asfquart~=0.1.12", + # https://github.com/apache/infrastructure-asfquart/pull/107 "asfquart @ git+https://github.com/raboof/infrastructure-asfquart@oauth-support-client-id-and-secret", "hypercorn", "pydantic~=2.11",