diff --git a/docs/reference/openapi.yaml b/docs/reference/openapi.yaml index 499e8cc52..1d94733ee 100644 --- a/docs/reference/openapi.yaml +++ b/docs/reference/openapi.yaml @@ -92,17 +92,17 @@ components: description: Client ID title: Client Id type: string + issuer: + description: URL of OIDC provider + title: Issuer + type: string logout_redirect_endpoint: default: '' description: The oidc endpoint required to logout title: Logout Redirect Endpoint type: string - well_known_url: - description: URL to fetch OIDC config from the provider - title: Well Known Url - type: string required: - - well_known_url + - issuer - client_id title: OIDCConfig type: object @@ -377,7 +377,7 @@ info: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0.html title: BlueAPI Control - version: 1.1.2 + version: 1.2.0 openapi: 3.1.0 paths: /config/oidc: diff --git a/helm/blueapi/config_schema.json b/helm/blueapi/config_schema.json index d54bc5357..7d695e0c8 100644 --- a/helm/blueapi/config_schema.json +++ b/helm/blueapi/config_schema.json @@ -299,9 +299,9 @@ "OIDCConfig": { "additionalProperties": false, "properties": { - "well_known_url": { - "description": "URL to fetch OIDC config from the provider", - "title": "Well Known Url", + "issuer": { + "description": "URL of OIDC provider", + "title": "Issuer", "type": "string" }, "client_id": { @@ -323,7 +323,7 @@ } }, "required": [ - "well_known_url", + "issuer", "client_id" ], "title": "OIDCConfig", diff --git a/helm/blueapi/values.schema.json b/helm/blueapi/values.schema.json index 1578c0dad..6226ed3a3 100644 --- a/helm/blueapi/values.schema.json +++ b/helm/blueapi/values.schema.json @@ -724,7 +724,7 @@ "title": "OIDCConfig", "type": "object", "required": [ - "well_known_url", + "issuer", "client_id" ], "properties": { @@ -739,16 +739,16 @@ "description": "Client ID", "type": "string" }, + "issuer": { + "title": "Issuer", + "description": "URL of OIDC provider", + "type": "string" + }, "logout_redirect_endpoint": { "title": "Logout Redirect Endpoint", "description": "The oidc endpoint required to logout", "default": "", "type": "string" - }, - "well_known_url": { - "title": "Well Known Url", - "description": "URL to fetch OIDC config from the provider", - "type": "string" } }, "additionalProperties": false diff --git a/src/blueapi/config.py b/src/blueapi/config.py index eb1b3122e..c6d49b22e 100644 --- a/src/blueapi/config.py +++ b/src/blueapi/config.py @@ -206,9 +206,7 @@ class ScratchConfig(BlueapiBaseModel): class OIDCConfig(BlueapiBaseModel): - well_known_url: str = Field( - description="URL to fetch OIDC config from the provider" - ) + issuer: str = Field(description="URL of OIDC provider") client_id: str = Field(description="Client ID") client_audience: str = Field(description="Client Audience(s)", default="blueapi") logout_redirect_endpoint: str = Field( @@ -217,7 +215,9 @@ class OIDCConfig(BlueapiBaseModel): @cached_property def _config_from_oidc_url(self) -> dict[str, Any]: - response: requests.Response = requests.get(self.well_known_url) + response: requests.Response = requests.get( + self.issuer + "/.well-known/openid-configuration" + ) response.raise_for_status() return response.json() @@ -231,10 +231,6 @@ def device_authorization_endpoint(self) -> str: def token_endpoint(self) -> str: return cast(str, self._config_from_oidc_url.get("token_endpoint")) - @cached_property - def issuer(self) -> str: - return cast(str, self._config_from_oidc_url.get("issuer")) - @cached_property def authorization_endpoint(self) -> str: return cast(str, self._config_from_oidc_url.get("authorization_endpoint")) diff --git a/src/blueapi/service/main.py b/src/blueapi/service/main.py index 74b6e8193..d29575665 100644 --- a/src/blueapi/service/main.py +++ b/src/blueapi/service/main.py @@ -58,7 +58,7 @@ from .runner import WorkerDispatcher #: API version to publish in OpenAPI schema -REST_API_VERSION = "1.1.2" +REST_API_VERSION = "1.2.0" LICENSE_INFO: dict[str, str] = { "name": "Apache 2.0", diff --git a/tests/conftest.py b/tests/conftest.py index b2f7cb947..81e411bf8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -56,18 +56,12 @@ def exporter() -> JsonObjectSpanExporter: return exporter -@pytest.fixture -def oidc_url() -> str: - return ( - "https://auth.example.com/realms/master/oidc/.well-known/openid-configuration" - ) +ISSUER = "https://auth.example.com/realms/master" @pytest.fixture -def oidc_config(oidc_url: str) -> OIDCConfig: - return OIDCConfig( - well_known_url=oidc_url, client_id="blueapi-cli", client_audience="blueapi" - ) +def oidc_config() -> OIDCConfig: + return OIDCConfig(issuer=ISSUER, client_id="blueapi-cli", client_audience="blueapi") CACHE_FILE = "blueapi_cache" @@ -88,7 +82,7 @@ def oidc_well_known() -> dict[str, Any]: "device_authorization_endpoint": "https://example.com/device_authorization", "authorization_endpoint": "https://example.com/authorization", "token_endpoint": "https://example.com/token", - "issuer": "https://example.com", + "issuer": ISSUER, "jwks_uri": "https://example.com/realms/master/protocol/openid-connect/certs", "end_session_endpoint": "https://example.com/end_session", "id_token_signing_alg_values_supported": ["RS256"], @@ -112,6 +106,7 @@ def _make_token( rsa_private_key: str, jwt_access_token: bool = False, valid_audience: bool = True, + issuer: str = ISSUER, ) -> dict[str, str]: now = time.time() @@ -119,7 +114,7 @@ def _make_token( "aud": "blueapi" if valid_audience else "invalid_audience", "exp": now + expires_in, "iat": now + issued_in, - "iss": "https://example.com", + "iss": issuer, "sub": "jd1", "name": "Jane Doe", "fedid": "jd1", @@ -244,7 +239,6 @@ def device_code() -> str: @pytest.fixture def mock_authn_server( - oidc_url: str, oidc_well_known: dict[str, Any], oidc_config: OIDCConfig, valid_token: dict[str, Any], @@ -258,7 +252,9 @@ def mock_authn_server( json=oidc_config.model_dump(), ) # Fetch well-known OIDC flow URLs from server - requests_mock.get(oidc_url, json=oidc_well_known) + requests_mock.get( + ISSUER + "/.well-known/openid-configuration", json=oidc_well_known + ) # When device flow begins, return a device_code requests_mock.post( oidc_well_known["device_authorization_endpoint"], diff --git a/tests/system_tests/config.yaml b/tests/system_tests/config.yaml index 11dcf5f05..a1f314070 100644 --- a/tests/system_tests/config.yaml +++ b/tests/system_tests/config.yaml @@ -19,6 +19,6 @@ tiled: enabled: true url: http://localhost:8407/api/v1 oidc: - well_known_url: "http://localhost:8081/realms/master/.well-known/openid-configuration" + issuer: "http://localhost:8081/realms/master" client_id: "ixx-cli-blueapi" client_audience: "blueapi" diff --git a/tests/system_tests/test_blueapi_system.py b/tests/system_tests/test_blueapi_system.py index c1b7ecc4c..fd574916c 100644 --- a/tests/system_tests/test_blueapi_system.py +++ b/tests/system_tests/test_blueapi_system.py @@ -81,8 +81,8 @@ # This client will give tokens for alice CLIENT_ID = "system-test-blueapi" CLIENT_SECRET = "secret" -KEYCLOAK_BASE_URL = "http://localhost:8081/" -OIDC_TOKEN_ENDPOINT = KEYCLOAK_BASE_URL + "realms/master/protocol/openid-connect/token" +KEYCLOAK_BASE_URL = "http://localhost:8081/realms/master" +OIDC_TOKEN_ENDPOINT = KEYCLOAK_BASE_URL + "/protocol/openid-connect/token" @pytest.fixture @@ -226,8 +226,7 @@ def test_cannot_access_endpoints( def test_can_get_oidc_config_without_auth(client_without_auth: BlueapiClient): assert client_without_auth.get_oidc_config() == OIDCConfig( - well_known_url=KEYCLOAK_BASE_URL - + "realms/master/.well-known/openid-configuration", + issuer=KEYCLOAK_BASE_URL, client_id="ixx-cli-blueapi", ) diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py index 2a49a1fe8..6f9bb2b4f 100644 --- a/tests/unit_tests/test_cli.py +++ b/tests/unit_tests/test_cli.py @@ -1025,7 +1025,7 @@ def test_login_success( result = runner.invoke(main, ["-c", config_with_auth, "login"]) assert ( "Logging in\n" - "Please login from this URL:- https://example.com/verify\n" + "Please login from this URL:- https://auth.example.com/realms/master/verify\n" "Logged in and cached new token\n" == result.output ) assert result.exit_code == 0 @@ -1064,7 +1064,7 @@ def test_login_when_cached_token_decode_fails( result = runner.invoke(main, ["-c", config_with_auth, "login"]) assert ( "Logging in\n" - "Please login from this URL:- https://example.com/verify\n" + "Please login from this URL:- https://auth.example.com/realms/master/verify\n" "Logged in and cached new token\n" in result.output ) assert result.exit_code == 0 diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index 26621502a..375d0df19 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -322,7 +322,7 @@ def test_config_yaml_parsed(temp_yaml_config_file): }, "numtracker": None, "oidc": { - "well_known_url": "https://auth.example.com/realms/sample/.well-known/openid-configuration", + "issuer": "https://auth.example.com/realms/sample", "client_id": "blueapi-client", "client_audience": "aud", "logout_redirect_endpoint": "", @@ -377,7 +377,7 @@ def test_config_yaml_parsed(temp_yaml_config_file): }, "numtracker": None, "oidc": { - "well_known_url": "https://auth.example.com/realms/sample/.well-known/openid-configuration", + "issuer": "https://auth.example.com/realms/sample", "client_id": "blueapi-client", "client_audience": "aud", "logout_redirect_endpoint": "", @@ -446,7 +446,7 @@ def test_config_yaml_parsed_complete(temp_yaml_config_file: dict): "api": {"host": "0.0.0.0", "port": 8001, "protocol": "http"}, "numtracker": None, "oidc": { - "well_known_url": "https://auth.example.com/realms/sample/.well-known/openid-configuration", + "issuer": "https://auth.example.com/realms/sample", "client_id": "blueapi-client", "client_audience": "aud", }, @@ -498,7 +498,6 @@ def test_oauth_config_model_post_init( oidc_config.authorization_endpoint == oidc_well_known["authorization_endpoint"] ) assert oidc_config.token_endpoint == oidc_well_known["token_endpoint"] - assert oidc_config.issuer == oidc_well_known["issuer"] assert oidc_config.jwks_uri == oidc_well_known["jwks_uri"] assert oidc_config.end_session_endpoint == oidc_well_known["end_session_endpoint"] diff --git a/tests/unit_tests/test_helm_chart.py b/tests/unit_tests/test_helm_chart.py index f6e34ddf5..0172e2128 100644 --- a/tests/unit_tests/test_helm_chart.py +++ b/tests/unit_tests/test_helm_chart.py @@ -69,7 +69,7 @@ ), logging=LoggingConfig(level="CRITICAL"), oidc=OIDCConfig( - well_known_url="foo.bar", + issuer="foo.bar", client_id="blueapi2", client_audience="blueapi++", ),