From 522da086d5321810099fd11eb882cd5f034a4858 Mon Sep 17 00:00:00 2001 From: "Felipe N. Schuch" Date: Thu, 12 Mar 2026 09:47:52 +0100 Subject: [PATCH 1/5] Revert "[main] chore(api): bump armasec (#905)" This reverts commit 65e531fc369757cb6ab373fa8afd40c693c5e53c. --- jobbergate-api/jobbergate_api/security.py | 5 +++-- jobbergate-api/pyproject.toml | 2 +- uv.lock | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/jobbergate-api/jobbergate_api/security.py b/jobbergate-api/jobbergate_api/security.py index e8d2fc2c..298e3e12 100644 --- a/jobbergate-api/jobbergate_api/security.py +++ b/jobbergate-api/jobbergate_api/security.py @@ -20,7 +20,6 @@ domain=settings.ARMASEC_DOMAIN, debug_logger=logger.debug if settings.ARMASEC_DEBUG else None, use_https=settings.ARMASEC_USE_HTTPS, - ignore_audience=True, ) @@ -95,7 +94,9 @@ def lockdown_with_identity( """ def dependency( - token_payload: Annotated[TokenPayload, Depends(guard.lockdown(*scopes, permission_mode=permission_mode))], + token_payload: Annotated[ + TokenPayload, Depends(guard.lockdown(*scopes, permission_mode=permission_mode)) + ], ) -> IdentityPayload: """ Provide an injectable function to lockdown a route and extract the identity payload. diff --git a/jobbergate-api/pyproject.toml b/jobbergate-api/pyproject.toml index 0d548fac..d406838f 100644 --- a/jobbergate-api/pyproject.toml +++ b/jobbergate-api/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ dependencies = [ "aioboto3>=15.5.0", "alembic>=1.17.2", - "armasec>=3.0.3", + "armasec>=3.0.2", "asyncpg>=0.31.0", "fastapi>=0.124.4", "fastapi-pagination>=0.15.3", diff --git a/uv.lock b/uv.lock index b5d53d5d..c3ff9118 100644 --- a/uv.lock +++ b/uv.lock @@ -285,7 +285,7 @@ wheels = [ [[package]] name = "armasec" -version = "3.0.3" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "auto-name-enum" }, @@ -300,9 +300,9 @@ dependencies = [ { name = "snick" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/79/cdc45753b386878547872b8b787ded012fe5a701558294c5aa8d60bc27cb/armasec-3.0.3.tar.gz", hash = "sha256:a8c0e5edc38a1581ed607494a17fb8bfb4573f716efe5b53a9546f9e61c037bd", size = 3011915, upload-time = "2026-02-17T17:23:55.512Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/2f/c9063d10b5ce5b643d26e8d191db2bf056606c7116c7f2aa00a5ce8cf93c/armasec-3.0.2.tar.gz", hash = "sha256:8db828bc5626de02b33eea259489ae35f309be345e4919b382a654c3ef5e61d1", size = 2998180, upload-time = "2025-09-17T21:28:30.686Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/50/6818db4ddb1e77164d0b696b6f24021e851fe9dea95e39564ce7b7202a85/armasec-3.0.3-py3-none-any.whl", hash = "sha256:76f5e09e43b04b83cd4fcbcfaa9ca81427b25134101edc3dde07796f7d5d2627", size = 35804, upload-time = "2026-02-17T17:23:56.797Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e6/56eb7e2876d0f64f46e2f00468f8489ccf465ef3cb4955418fb930bf8ef2/armasec-3.0.2-py3-none-any.whl", hash = "sha256:da4cf534a1119c1e4a419757374e164cfb78ffc44205a14f3b90039823d32f5a", size = 35503, upload-time = "2025-09-17T21:28:29.458Z" }, ] [[package]] @@ -1259,7 +1259,7 @@ requires-dist = [ { name = "aio-pika", specifier = ">=9.5.8" }, { name = "aioboto3", specifier = ">=15.5.0" }, { name = "alembic", specifier = ">=1.17.2" }, - { name = "armasec", specifier = ">=3.0.3" }, + { name = "armasec", specifier = ">=3.0.2" }, { name = "asyncpg", specifier = ">=0.31.0" }, { name = "auto-name-enum", specifier = ">=3.0.0" }, { name = "fastapi", specifier = ">=0.124.4" }, From 6c9d83bca186555cf054cc2b0d0da743113826e9 Mon Sep 17 00:00:00 2001 From: "Felipe N. Schuch" Date: Thu, 12 Mar 2026 09:48:08 +0100 Subject: [PATCH 2/5] Revert "change(api)!:Remove DomainConfig from Armasec client (#927)" This reverts commit 0173a56c807e835ba55a1ebafb0c4bfa0fd9f36c. --- jobbergate-api/jobbergate_api/config.py | 6 ++++ jobbergate-api/jobbergate_api/security.py | 39 ++++++++++++++++++-- jobbergate-api/tests/test_security.py | 43 +++++++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/jobbergate-api/jobbergate_api/config.py b/jobbergate-api/jobbergate_api/config.py index 8aca0c1a..bfbcc489 100644 --- a/jobbergate-api/jobbergate_api/config.py +++ b/jobbergate-api/jobbergate_api/config.py @@ -80,6 +80,12 @@ class Settings(BaseSettings): ARMASEC_DOMAIN: str ARMASEC_USE_HTTPS: bool = Field(True) ARMASEC_DEBUG: bool = Field(False) + ARMASEC_ADMIN_DOMAIN: Optional[str] = None + ARMASEC_ADMIN_MATCH_KEY: Optional[str] = None + ARMASEC_ADMIN_MATCH_VALUE: Optional[str] = None + + # Key to custom claims packaged with Auth0 tokens + IDENTITY_CLAIMS_KEY: str = "https://omnivector.solutions" # Sentry configuration SENTRY_DSN: Optional[HttpUrl] = None diff --git a/jobbergate-api/jobbergate_api/security.py b/jobbergate-api/jobbergate_api/security.py index 298e3e12..8c8d9652 100644 --- a/jobbergate-api/jobbergate_api/security.py +++ b/jobbergate-api/jobbergate_api/security.py @@ -7,6 +7,7 @@ from typing import Annotated from armasec import Armasec, TokenPayload +from armasec.schemas import DomainConfig from armasec.token_security import PermissionMode from buzz import check_expressions from fastapi import Depends, HTTPException, status @@ -16,10 +17,44 @@ from jobbergate_api.config import settings +def get_domain_configs() -> list[DomainConfig]: + """ + Return a list of DomainConfig objects based on the input variables for the Settings class. + """ + # make type checkers happy + assert settings.ARMASEC_DOMAIN is not None + + domain_configs = [ + DomainConfig( + domain=settings.ARMASEC_DOMAIN, + use_https=settings.ARMASEC_USE_HTTPS, + ) + ] + if all( + [ + settings.ARMASEC_ADMIN_DOMAIN, + settings.ARMASEC_ADMIN_MATCH_KEY, + settings.ARMASEC_ADMIN_MATCH_VALUE, + ] + ): + # make type checkers happy + assert settings.ARMASEC_ADMIN_DOMAIN is not None + assert settings.ARMASEC_ADMIN_MATCH_KEY is not None + assert settings.ARMASEC_ADMIN_MATCH_VALUE is not None + + domain_configs.append( + DomainConfig( + domain=settings.ARMASEC_ADMIN_DOMAIN, + use_https=settings.ARMASEC_USE_HTTPS, + match_keys={settings.ARMASEC_ADMIN_MATCH_KEY: settings.ARMASEC_ADMIN_MATCH_VALUE}, + ) + ) + return domain_configs + + guard = Armasec( - domain=settings.ARMASEC_DOMAIN, + domain_configs=get_domain_configs(), debug_logger=logger.debug if settings.ARMASEC_DEBUG else None, - use_https=settings.ARMASEC_USE_HTTPS, ) diff --git a/jobbergate-api/tests/test_security.py b/jobbergate-api/tests/test_security.py index 3c1d8f46..4a9bbe41 100644 --- a/jobbergate-api/tests/test_security.py +++ b/jobbergate-api/tests/test_security.py @@ -2,16 +2,59 @@ Test the security module. """ +from unittest.mock import patch + import pytest from jobbergate_api.security import ( HTTPException, IdentityPayload, TokenPayload, + get_domain_configs, lockdown_with_identity, + settings, ) +def test_get_domain_configs__loads_only_base_settings(): + """Check if the correct domain configuration is loaded when only one domain is provided.""" + with ( + patch.object(settings, "ARMASEC_DOMAIN", new="foo.io"), + ): + domain_configs = get_domain_configs() + + assert len(domain_configs) == 1 + first_config = domain_configs.pop() + assert first_config.domain == "foo.io" + + +def test_get_domain_configs__loads_admin_settings_if_all_are_present(): + """Check if the correct domain configuration is loaded when two domains are provided.""" + with ( + patch.object(settings, "ARMASEC_DOMAIN", new="foo.io"), + patch.object(settings, "ARMASEC_ADMIN_DOMAIN", new="admin.io"), + ): + domain_configs = get_domain_configs() + + assert len(domain_configs) == 1 + first_config = domain_configs.pop() + assert first_config.domain == "foo.io" + + with ( + patch.object(settings, "ARMASEC_DOMAIN", new="foo.io"), + patch.object(settings, "ARMASEC_ADMIN_DOMAIN", new="admin.io"), + patch.object(settings, "ARMASEC_ADMIN_MATCH_KEY", new="foo"), + patch.object(settings, "ARMASEC_ADMIN_MATCH_VALUE", new="bar"), + ): + domain_configs = get_domain_configs() + + assert len(domain_configs) == 2 + (first_config, second_config) = domain_configs + assert first_config.domain == "foo.io" + assert second_config.domain == "admin.io" + assert second_config.match_keys == {"foo": "bar"} + + def test_lockdown_with_identity__success(): """Check if the lockdown_with_identity decorator returns the correct identity.""" token_raw_data = {"sub": "dummy-sub"} From 7155e9750e99ca99e98f08bfd38b7d69476e7f00 Mon Sep 17 00:00:00 2001 From: "Felipe N. Schuch" Date: Wed, 18 Feb 2026 16:29:56 -0300 Subject: [PATCH 3/5] chore(api): bump armasec (#903) --- jobbergate-api/jobbergate_api/security.py | 1 + jobbergate-api/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/jobbergate-api/jobbergate_api/security.py b/jobbergate-api/jobbergate_api/security.py index 8c8d9652..7bdee317 100644 --- a/jobbergate-api/jobbergate_api/security.py +++ b/jobbergate-api/jobbergate_api/security.py @@ -55,6 +55,7 @@ def get_domain_configs() -> list[DomainConfig]: guard = Armasec( domain_configs=get_domain_configs(), debug_logger=logger.debug if settings.ARMASEC_DEBUG else None, + ignore_audience=True, ) diff --git a/jobbergate-api/pyproject.toml b/jobbergate-api/pyproject.toml index d406838f..0d548fac 100644 --- a/jobbergate-api/pyproject.toml +++ b/jobbergate-api/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ dependencies = [ "aioboto3>=15.5.0", "alembic>=1.17.2", - "armasec>=3.0.2", + "armasec>=3.0.3", "asyncpg>=0.31.0", "fastapi>=0.124.4", "fastapi-pagination>=0.15.3", From 1f698cdd391abfbfafa95e4244ef263b6faefe30 Mon Sep 17 00:00:00 2001 From: "Felipe N. Schuch" Date: Thu, 12 Mar 2026 09:51:21 +0100 Subject: [PATCH 4/5] fix(api): properly address `ignore_audience=True` from armasec 3.0.3 (#915) --- jobbergate-api/jobbergate_api/security.py | 3 ++- uv.lock | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/jobbergate-api/jobbergate_api/security.py b/jobbergate-api/jobbergate_api/security.py index 7bdee317..d242a4db 100644 --- a/jobbergate-api/jobbergate_api/security.py +++ b/jobbergate-api/jobbergate_api/security.py @@ -28,6 +28,7 @@ def get_domain_configs() -> list[DomainConfig]: DomainConfig( domain=settings.ARMASEC_DOMAIN, use_https=settings.ARMASEC_USE_HTTPS, + ignore_audience=True, ) ] if all( @@ -47,6 +48,7 @@ def get_domain_configs() -> list[DomainConfig]: domain=settings.ARMASEC_ADMIN_DOMAIN, use_https=settings.ARMASEC_USE_HTTPS, match_keys={settings.ARMASEC_ADMIN_MATCH_KEY: settings.ARMASEC_ADMIN_MATCH_VALUE}, + ignore_audience=True, ) ) return domain_configs @@ -55,7 +57,6 @@ def get_domain_configs() -> list[DomainConfig]: guard = Armasec( domain_configs=get_domain_configs(), debug_logger=logger.debug if settings.ARMASEC_DEBUG else None, - ignore_audience=True, ) diff --git a/uv.lock b/uv.lock index c3ff9118..b5d53d5d 100644 --- a/uv.lock +++ b/uv.lock @@ -285,7 +285,7 @@ wheels = [ [[package]] name = "armasec" -version = "3.0.2" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "auto-name-enum" }, @@ -300,9 +300,9 @@ dependencies = [ { name = "snick" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/2f/c9063d10b5ce5b643d26e8d191db2bf056606c7116c7f2aa00a5ce8cf93c/armasec-3.0.2.tar.gz", hash = "sha256:8db828bc5626de02b33eea259489ae35f309be345e4919b382a654c3ef5e61d1", size = 2998180, upload-time = "2025-09-17T21:28:30.686Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/79/cdc45753b386878547872b8b787ded012fe5a701558294c5aa8d60bc27cb/armasec-3.0.3.tar.gz", hash = "sha256:a8c0e5edc38a1581ed607494a17fb8bfb4573f716efe5b53a9546f9e61c037bd", size = 3011915, upload-time = "2026-02-17T17:23:55.512Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/56eb7e2876d0f64f46e2f00468f8489ccf465ef3cb4955418fb930bf8ef2/armasec-3.0.2-py3-none-any.whl", hash = "sha256:da4cf534a1119c1e4a419757374e164cfb78ffc44205a14f3b90039823d32f5a", size = 35503, upload-time = "2025-09-17T21:28:29.458Z" }, + { url = "https://files.pythonhosted.org/packages/92/50/6818db4ddb1e77164d0b696b6f24021e851fe9dea95e39564ce7b7202a85/armasec-3.0.3-py3-none-any.whl", hash = "sha256:76f5e09e43b04b83cd4fcbcfaa9ca81427b25134101edc3dde07796f7d5d2627", size = 35804, upload-time = "2026-02-17T17:23:56.797Z" }, ] [[package]] @@ -1259,7 +1259,7 @@ requires-dist = [ { name = "aio-pika", specifier = ">=9.5.8" }, { name = "aioboto3", specifier = ">=15.5.0" }, { name = "alembic", specifier = ">=1.17.2" }, - { name = "armasec", specifier = ">=3.0.2" }, + { name = "armasec", specifier = ">=3.0.3" }, { name = "asyncpg", specifier = ">=0.31.0" }, { name = "auto-name-enum", specifier = ">=3.0.0" }, { name = "fastapi", specifier = ">=0.124.4" }, From ab0c0aeef277a99e01cc580893f8b739cb6e7754 Mon Sep 17 00:00:00 2001 From: "Felipe N. Schuch" Date: Thu, 12 Mar 2026 10:03:57 +0100 Subject: [PATCH 5/5] update changelog --- changes/api/941.changed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/api/941.changed.md diff --git a/changes/api/941.changed.md b/changes/api/941.changed.md new file mode 100644 index 00000000..887a7499 --- /dev/null +++ b/changes/api/941.changed.md @@ -0,0 +1 @@ +Reintroduced armasec domain config