diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml index a6627949ee..7aad2c7958 100644 --- a/.github/workflows/ci-main.yml +++ b/.github/workflows/ci-main.yml @@ -282,10 +282,12 @@ jobs: fail-fast: false matrix: job: - - name: basic - glob: tests/openid_conformance/test_basic.py - - name: implicit - glob: tests/openid_conformance/test_implicit.py + - name: oidc_basic + glob: tests/openid_conformance/test_oidc_basic.py + - name: oidc_implicit + glob: tests/openid_conformance/test_oidc_implicit.py + - name: ssf_transmitter + glob: tests/openid_conformance/test_ssf_transmitter.py steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 - name: Setup authentik env diff --git a/authentik/enterprise/providers/ssf/migrations/0002_ssfprovider_push_verify_certificates_and_more.py b/authentik/enterprise/providers/ssf/migrations/0002_ssfprovider_push_verify_certificates_and_more.py index 78171d68e5..19dbf6444e 100644 --- a/authentik/enterprise/providers/ssf/migrations/0002_ssfprovider_push_verify_certificates_and_more.py +++ b/authentik/enterprise/providers/ssf/migrations/0002_ssfprovider_push_verify_certificates_and_more.py @@ -1,6 +1,7 @@ # Generated by Django 5.2.12 on 2026-04-04 16:58 from django.db import migrations, models +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -40,4 +41,109 @@ class Migration(migrations.Migration): ] ), ), + migrations.AlterField( + model_name="stream", + name="events_requested", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.TextField( + choices=[ + ( + "https://schemas.openid.net/secevent/caep/event-type/session-revoked", + "Caep Session Revoked", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/token-claims-change", + "Caep Token Claims Change", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/credential-change", + "Caep Credential Change", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/assurance-level-change", + "Caep Assurance Level Change", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/device-compliance-change", + "Caep Device Compliance Change", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/session-established", + "Caep Session Established", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/session-presented", + "Caep Session Presented", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/risk-level-change", + "Caep Risk Level Change", + ), + ( + "https://schemas.openid.net/secevent/ssf/event-type/verification", + "Set Verification", + ), + ] + ), + default=list, + size=None, + ), + ), + migrations.AlterField( + model_name="stream", + name="status", + field=models.TextField( + choices=[ + ("enabled", "Enabled"), + ("paused", "Paused"), + ("disabled", "Disabled"), + ("disabled_deleted", "Disabled Deleted"), + ], + default="enabled", + ), + ), + migrations.AlterField( + model_name="streamevent", + name="type", + field=models.TextField( + choices=[ + ( + "https://schemas.openid.net/secevent/caep/event-type/session-revoked", + "Caep Session Revoked", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/token-claims-change", + "Caep Token Claims Change", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/credential-change", + "Caep Credential Change", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/assurance-level-change", + "Caep Assurance Level Change", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/device-compliance-change", + "Caep Device Compliance Change", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/session-established", + "Caep Session Established", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/session-presented", + "Caep Session Presented", + ), + ( + "https://schemas.openid.net/secevent/caep/event-type/risk-level-change", + "Caep Risk Level Change", + ), + ( + "https://schemas.openid.net/secevent/ssf/event-type/verification", + "Set Verification", + ), + ] + ), + ), ] diff --git a/authentik/enterprise/providers/ssf/models.py b/authentik/enterprise/providers/ssf/models.py index 03864ef289..77b7b1454b 100644 --- a/authentik/enterprise/providers/ssf/models.py +++ b/authentik/enterprise/providers/ssf/models.py @@ -24,8 +24,31 @@ class EventTypes(models.TextChoices): """SSF Event types supported by authentik""" CAEP_SESSION_REVOKED = "https://schemas.openid.net/secevent/caep/event-type/session-revoked" + """https://openid.net/specs/openid-caep-1_0-final.html#section-3.1""" + CAEP_TOKEN_CLAIMS_CHANGE = ( + "https://schemas.openid.net/secevent/caep/event-type/token-claims-change" + ) + """https://openid.net/specs/openid-caep-1_0-final.html#section-3.2""" CAEP_CREDENTIAL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/credential-change" + """https://openid.net/specs/openid-caep-1_0-final.html#section-3.3""" + CAEP_ASSURANCE_LEVEL_CHANGE = ( + "https://schemas.openid.net/secevent/caep/event-type/assurance-level-change" + ) + """https://openid.net/specs/openid-caep-1_0-final.html#section-3.4""" + CAEP_DEVICE_COMPLIANCE_CHANGE = ( + "https://schemas.openid.net/secevent/caep/event-type/device-compliance-change" + ) + """https://openid.net/specs/openid-caep-1_0-final.html#section-3.5""" + CAEP_SESSION_ESTABLISHED = ( + "https://schemas.openid.net/secevent/caep/event-type/session-established" + ) + """https://openid.net/specs/openid-caep-1_0-final.html#section-3.6""" + CAEP_SESSION_PRESENTED = "https://schemas.openid.net/secevent/caep/event-type/session-presented" + """https://openid.net/specs/openid-caep-1_0-final.html#section-3.7""" + CAEP_RISK_LEVEL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/risk-level-change" + """https://openid.net/specs/openid-caep-1_0-final.html#section-3.8""" SET_VERIFICATION = "https://schemas.openid.net/secevent/ssf/event-type/verification" + """https://openid.net/specs/openid-sharedsignals-framework-1_0.html#section-8.1.4.1""" class DeliveryMethods(models.TextChoices): @@ -46,10 +69,12 @@ class SSFEventStatus(models.TextChoices): class StreamStatus(models.TextChoices): + """SSF Stream status""" ENABLED = "enabled" PAUSED = "paused" DISABLED = "disabled" + DISABLED_DELETED = "disabled_deleted" class SSFProvider(TasksModel, BackchannelProvider): diff --git a/authentik/enterprise/providers/ssf/tasks.py b/authentik/enterprise/providers/ssf/tasks.py index 5b70295313..47fca246b9 100644 --- a/authentik/enterprise/providers/ssf/tasks.py +++ b/authentik/enterprise/providers/ssf/tasks.py @@ -108,13 +108,13 @@ def send_ssf_event(stream_uuid: UUID, event_data: dict[str, Any]): event.save() self.info("Event successfully sent", status=response.status_code) # Cleanup, if we were the last pending message for this stream and it has been deleted - # (status=StreamStatus.DISABLED), then we can delete the stream + # (status=StreamStatus.DISABLED_DELETED), then we can delete the stream if ( not StreamEvent.objects.filter( stream=stream, status__in=[SSFEventStatus.PENDING_FAILED, SSFEventStatus.PENDING_NEW], ).exists() - and stream.status == StreamStatus.DISABLED + and stream.status == StreamStatus.DISABLED_DELETED ): LOGGER.info( "Deleting inactive stream as all pending messages were sent.", stream=stream diff --git a/authentik/enterprise/providers/ssf/tests/test_auth.py b/authentik/enterprise/providers/ssf/tests/test_auth.py index 438328996d..6946ea4eeb 100644 --- a/authentik/enterprise/providers/ssf/tests/test_auth.py +++ b/authentik/enterprise/providers/ssf/tests/test_auth.py @@ -62,7 +62,7 @@ class TestSSFAuth(APITestCase): self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED) self.assertEqual( event.payload["events"], - {"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}}, + {"https://schemas.openid.net/secevent/ssf/event-type/verification": {}}, ) def test_stream_add_oidc(self): @@ -115,7 +115,7 @@ class TestSSFAuth(APITestCase): self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED) self.assertEqual( event.payload["events"], - {"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}}, + {"https://schemas.openid.net/secevent/ssf/event-type/verification": {}}, ) def test_token_invalid(self): diff --git a/authentik/enterprise/providers/ssf/tests/test_stream.py b/authentik/enterprise/providers/ssf/tests/test_stream.py index 9d9f38fe75..dba7d41e71 100644 --- a/authentik/enterprise/providers/ssf/tests/test_stream.py +++ b/authentik/enterprise/providers/ssf/tests/test_stream.py @@ -54,7 +54,7 @@ class TestStream(APITestCase): self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED) self.assertEqual( event.payload["events"], - {"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}}, + {"https://schemas.openid.net/secevent/ssf/event-type/verification": {}}, ) def test_stream_add_poll(self): @@ -96,7 +96,7 @@ class TestStream(APITestCase): ) self.assertEqual(res.status_code, 204) stream.refresh_from_db() - self.assertEqual(stream.status, StreamStatus.DISABLED) + self.assertEqual(stream.status, StreamStatus.DISABLED_DELETED) def test_stream_get(self): """get stream""" @@ -225,3 +225,26 @@ class TestStream(APITestCase): HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}", ) self.assertEqual(res.status_code, 404) + + def test_stream_status_update(self): + stream = Stream.objects.create(provider=self.provider) + res = self.client.post( + reverse( + "authentik_providers_ssf:stream-status", + kwargs={"application_slug": self.application.slug}, + ), + data={ + "stream_id": str(stream.pk), + "status": StreamStatus.DISABLED, + }, + HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}", + ) + self.assertEqual(res.status_code, 200) + stream.refresh_from_db() + self.assertJSONEqual( + res.content, + { + "stream_id": str(stream.pk), + "status": str(stream.status), + }, + ) diff --git a/authentik/enterprise/providers/ssf/tests/test_tasks.py b/authentik/enterprise/providers/ssf/tests/test_tasks.py index 82e3d5346b..1deebca18f 100644 --- a/authentik/enterprise/providers/ssf/tests/test_tasks.py +++ b/authentik/enterprise/providers/ssf/tests/test_tasks.py @@ -33,7 +33,7 @@ class TestTasks(APITestCase): ) event_data = stream.prepare_event_payload( EventTypes.SET_VERIFICATION, - {"state": None}, + {}, sub_id={"format": "opaque", "id": str(stream.uuid)}, ) with Mocker() as mocker: @@ -46,7 +46,7 @@ class TestTasks(APITestCase): ) jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False}) self.assertEqual(jwt["header"]["typ"], "secevent+jwt") - self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"]) + self.assertEqual(jwt["payload"]["events"][EventTypes.SET_VERIFICATION], {}) def test_push_auth(self): auth = generate_id() @@ -58,7 +58,7 @@ class TestTasks(APITestCase): ) event_data = stream.prepare_event_payload( EventTypes.SET_VERIFICATION, - {"state": None}, + {}, sub_id={"format": "opaque", "id": str(stream.uuid)}, ) with Mocker() as mocker: @@ -72,7 +72,7 @@ class TestTasks(APITestCase): ) jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False}) self.assertEqual(jwt["header"]["typ"], "secevent+jwt") - self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"]) + self.assertEqual(jwt["payload"]["events"][EventTypes.SET_VERIFICATION], {}) def test_push_stream_disable(self): auth = generate_id() @@ -81,11 +81,11 @@ class TestTasks(APITestCase): delivery_method=DeliveryMethods.RFC_PUSH, endpoint_url="http://localhost/ssf-push", authorization_header=auth, - status=StreamStatus.DISABLED, + status=StreamStatus.DISABLED_DELETED, ) event_data = stream.prepare_event_payload( EventTypes.SET_VERIFICATION, - {"state": None}, + {}, sub_id={"format": "opaque", "id": str(stream.uuid)}, ) with Mocker() as mocker: @@ -95,7 +95,7 @@ class TestTasks(APITestCase): ).get_result(block=True, timeout=1) jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False}) self.assertEqual(jwt["header"]["typ"], "secevent+jwt") - self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"]) + self.assertEqual(jwt["payload"]["events"][EventTypes.SET_VERIFICATION], {}) self.assertFalse(Stream.objects.filter(pk=stream.pk).exists()) def test_push_error(self): @@ -106,7 +106,7 @@ class TestTasks(APITestCase): ) event_data = stream.prepare_event_payload( EventTypes.SET_VERIFICATION, - {"state": None}, + {}, sub_id={"format": "opaque", "id": str(stream.uuid)}, ) with Mocker() as mocker: diff --git a/authentik/enterprise/providers/ssf/views/base.py b/authentik/enterprise/providers/ssf/views/base.py index 6c5dd00626..2ff472904c 100644 --- a/authentik/enterprise/providers/ssf/views/base.py +++ b/authentik/enterprise/providers/ssf/views/base.py @@ -24,10 +24,10 @@ class SSFView(APIView): class SSFStreamView(SSFView): - def get_object(self, any_status=False) -> Stream: - streams = Stream.objects.filter(provider=self.provider) - if not any_status: - streams = streams.filter(status__in=[StreamStatus.ENABLED, StreamStatus.PAUSED]) + def get_object(self) -> Stream: + streams = Stream.objects.filter(provider=self.provider).exclude( + status=StreamStatus.DISABLED_DELETED + ) if "stream_id" in self.request.query_params: streams = streams.filter(pk=self.request.query_params["stream_id"]) if "stream_id" in self.request.data: diff --git a/authentik/enterprise/providers/ssf/views/stream.py b/authentik/enterprise/providers/ssf/views/stream.py index 45b2ce9cf0..d143568847 100644 --- a/authentik/enterprise/providers/ssf/views/stream.py +++ b/authentik/enterprise/providers/ssf/views/stream.py @@ -1,6 +1,6 @@ from uuid import uuid4 -from django.http import HttpRequest +from django.http import Http404, HttpRequest from django.urls import reverse from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField @@ -106,7 +106,11 @@ class StreamResponseSerializer(PassiveSerializer): } def get_events_supported(self, instance: Stream) -> list[str]: - return [x.value for x in EventTypes] + return [ + EventTypes.CAEP_SESSION_REVOKED, + EventTypes.CAEP_CREDENTIAL_CHANGE, + EventTypes.SET_VERIFICATION, + ] class StreamView(SSFStreamView): @@ -128,10 +132,9 @@ class StreamView(SSFStreamView): LOGGER.info("Sending verification event", stream=instance) send_ssf_events( EventTypes.SET_VERIFICATION, - { - "state": None, - }, + {}, stream_filter={"pk": instance.uuid}, + request=request, sub_id={"format": "opaque", "id": str(instance.uuid)}, ) response = StreamResponseSerializer(instance=instance, context={"request": request}).data @@ -159,7 +162,9 @@ class StreamView(SSFStreamView): def delete(self, request: Request, *args, **kwargs) -> Response: stream = self.get_object() - stream.status = StreamStatus.DISABLED + if stream.status == StreamStatus.DISABLED_DELETED: + raise Http404 + stream.status = StreamStatus.DISABLED_DELETED stream.save() return Response(status=204) @@ -175,6 +180,7 @@ class StreamVerifyView(SSFStreamView): "state": state, }, stream_filter={"pk": stream.uuid}, + request=request, sub_id={"format": "opaque", "id": str(stream.uuid)}, ) return Response(status=204) @@ -182,8 +188,25 @@ class StreamVerifyView(SSFStreamView): class StreamStatusView(SSFStreamView): + class StreamStatusSerializer(PassiveSerializer): + stream_id = CharField() + status = ChoiceField(choices=StreamStatus.choices) + def get(self, request: Request, *args, **kwargs): - stream = self.get_object(any_status=True) + stream = self.get_object() + return Response( + { + "stream_id": str(stream.pk), + "status": str(stream.status), + } + ) + + def post(self, request: Request, *args, **kwargs): + stream = self.get_object() + serializer = self.StreamStatusSerializer(stream, data=request.data) + serializer.is_valid(raise_exception=True) + stream.status = serializer.validated_data["status"] + stream.save() return Response( { "stream_id": str(stream.pk), diff --git a/packages/client-ts/src/models/EventsRequestedEnum.ts b/packages/client-ts/src/models/EventsRequestedEnum.ts index b8946c5d92..be742865f2 100644 --- a/packages/client-ts/src/models/EventsRequestedEnum.ts +++ b/packages/client-ts/src/models/EventsRequestedEnum.ts @@ -19,8 +19,20 @@ export const EventsRequestedEnum = { HttpsSchemasOpenidNetSeceventCaepEventTypeSessionRevoked: "https://schemas.openid.net/secevent/caep/event-type/session-revoked", + HttpsSchemasOpenidNetSeceventCaepEventTypeTokenClaimsChange: + "https://schemas.openid.net/secevent/caep/event-type/token-claims-change", HttpsSchemasOpenidNetSeceventCaepEventTypeCredentialChange: "https://schemas.openid.net/secevent/caep/event-type/credential-change", + HttpsSchemasOpenidNetSeceventCaepEventTypeAssuranceLevelChange: + "https://schemas.openid.net/secevent/caep/event-type/assurance-level-change", + HttpsSchemasOpenidNetSeceventCaepEventTypeDeviceComplianceChange: + "https://schemas.openid.net/secevent/caep/event-type/device-compliance-change", + HttpsSchemasOpenidNetSeceventCaepEventTypeSessionEstablished: + "https://schemas.openid.net/secevent/caep/event-type/session-established", + HttpsSchemasOpenidNetSeceventCaepEventTypeSessionPresented: + "https://schemas.openid.net/secevent/caep/event-type/session-presented", + HttpsSchemasOpenidNetSeceventCaepEventTypeRiskLevelChange: + "https://schemas.openid.net/secevent/caep/event-type/risk-level-change", HttpsSchemasOpenidNetSeceventSsfEventTypeVerification: "https://schemas.openid.net/secevent/ssf/event-type/verification", UnknownDefaultOpenApi: "11184809", diff --git a/packages/client-ts/src/models/SSFStreamStatusEnum.ts b/packages/client-ts/src/models/SSFStreamStatusEnum.ts index 0a49d84a3c..c6c53e2e4c 100644 --- a/packages/client-ts/src/models/SSFStreamStatusEnum.ts +++ b/packages/client-ts/src/models/SSFStreamStatusEnum.ts @@ -20,6 +20,7 @@ export const SSFStreamStatusEnum = { Enabled: "enabled", Paused: "paused", Disabled: "disabled", + DisabledDeleted: "disabled_deleted", UnknownDefaultOpenApi: "11184809", } as const; export type SSFStreamStatusEnum = (typeof SSFStreamStatusEnum)[keyof typeof SSFStreamStatusEnum]; diff --git a/schema.yml b/schema.yml index 06aa704f65..282f246bc2 100644 --- a/schema.yml +++ b/schema.yml @@ -39031,7 +39031,13 @@ components: EventsRequestedEnum: enum: - https://schemas.openid.net/secevent/caep/event-type/session-revoked + - https://schemas.openid.net/secevent/caep/event-type/token-claims-change - https://schemas.openid.net/secevent/caep/event-type/credential-change + - https://schemas.openid.net/secevent/caep/event-type/assurance-level-change + - https://schemas.openid.net/secevent/caep/event-type/device-compliance-change + - https://schemas.openid.net/secevent/caep/event-type/session-established + - https://schemas.openid.net/secevent/caep/event-type/session-presented + - https://schemas.openid.net/secevent/caep/event-type/risk-level-change - https://schemas.openid.net/secevent/ssf/event-type/verification type: string ExpiringBaseGrantModel: @@ -55618,6 +55624,7 @@ components: - enabled - paused - disabled + - disabled_deleted type: string Schedule: type: object diff --git a/tests/openid_conformance/base.py b/tests/openid_conformance/base.py index 36485b012b..d86a81d608 100644 --- a/tests/openid_conformance/base.py +++ b/tests/openid_conformance/base.py @@ -1,6 +1,7 @@ from os import makedirs from pathlib import Path from time import sleep +from typing import Any from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as ec @@ -64,27 +65,27 @@ class TestOpenIDConformance(SeleniumTestCase): "client_registration": "static_client", } - def run_test(self, test_name: str, test_plan_config: dict): - # Create a Conformance instance... + def run_test( + self, test_name: str, test_plan_config: dict[str, Any], test_variant: dict[str, Any] + ): self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False) test_plan = self.conformance.create_test_plan( test_name, test_plan_config, - self.test_variant, + test_variant, ) plan_id = test_plan["id"] for test in test_plan["modules"]: - with self.subTest(test["testModule"], **test["variant"]): - # Fetch name and variant of the next test to run - module_name = test["testModule"] - variant = test["variant"] - module_instance = self.conformance.create_test_from_plan_with_variant( - plan_id, module_name, variant - ) - module_id = module_instance["id"] - self.run_single_test(module_id) - self.conformance.wait_for_state(module_id, ["FINISHED"], timeout=self.wait_timeout) + # Fetch name and variant of the next test to run + module_name = test["testModule"] + variant = test["variant"] + module_instance = self.conformance.create_test_from_plan_with_variant( + plan_id, module_name, variant + ) + module_id = module_instance["id"] + self.run_single_test(module_id) + self.conformance.wait_for_state(module_id, ["FINISHED"], timeout=self.wait_timeout) sleep(2) self.conformance.export_html(plan_id, Path(__file__).parent / "exports") diff --git a/tests/openid_conformance/compose.yml b/tests/openid_conformance/compose.yml index 9d8debe497..aa27694e5a 100644 --- a/tests/openid_conformance/compose.yml +++ b/tests/openid_conformance/compose.yml @@ -2,14 +2,14 @@ services: mongodb: image: mongo:6.0.13 nginx: - image: ghcr.io/beryju/oidc-conformance-suite-nginx:v5.1.41 + image: ghcr.io/beryju/oidc-conformance-suite-nginx:v5.1.43 ports: - "8443:8443" - "8444:8444" depends_on: - server server: - image: ghcr.io/beryju/oidc-conformance-suite-server:v5.1.41 + image: ghcr.io/beryju/oidc-conformance-suite-server:v5.1.43 ports: - "9999:9999" extra_hosts: @@ -19,8 +19,8 @@ services: -Xdebug -Xrunjdwp:transport=dt_socket,address=*:9999,server=y,suspend=n -jar /server/fapi-test-suite.jar -Djdk.tls.maxHandshakeMessageSize=65536 - --fintechlabs.base_url=https://host.docker.internal:8443 - --fintechlabs.base_mtls_url=https://host.docker.internal:8444 + --fintechlabs.base_url=https://localhost:8443 + --fintechlabs.base_mtls_url=https://localhost:8444 --fintechlabs.devmode=true --fintechlabs.startredir=true links: diff --git a/tests/openid_conformance/test_basic.py b/tests/openid_conformance/test_oidc_basic.py similarity index 64% rename from tests/openid_conformance/test_basic.py rename to tests/openid_conformance/test_oidc_basic.py index d72d828138..1358c66b75 100644 --- a/tests/openid_conformance/test_basic.py +++ b/tests/openid_conformance/test_oidc_basic.py @@ -6,5 +6,6 @@ class TestOpenIDConformanceBasic(TestOpenIDConformance): @retry() def test_oidcc_basic_certification_test(self): - test_plan_name = "oidcc-basic-certification-test-plan" - self.run_test(test_plan_name, self.test_plan_config) + self.run_test( + "oidcc-basic-certification-test-plan", self.test_plan_config, self.test_variant + ) diff --git a/tests/openid_conformance/test_implicit.py b/tests/openid_conformance/test_oidc_implicit.py similarity index 64% rename from tests/openid_conformance/test_implicit.py rename to tests/openid_conformance/test_oidc_implicit.py index 424836de92..f66da85d5b 100644 --- a/tests/openid_conformance/test_implicit.py +++ b/tests/openid_conformance/test_oidc_implicit.py @@ -6,5 +6,6 @@ class TestOpenIDConformanceImplicit(TestOpenIDConformance): @retry() def test_oidcc_implicit_certification_test_plan(self): - test_plan_name = "oidcc-implicit-certification-test-plan" - self.run_test(test_plan_name, self.test_plan_config) + self.run_test( + "oidcc-implicit-certification-test-plan", self.test_plan_config, self.test_variant + ) diff --git a/tests/openid_conformance/test_ssf_transmitter.py b/tests/openid_conformance/test_ssf_transmitter.py new file mode 100644 index 0000000000..6a8760ac86 --- /dev/null +++ b/tests/openid_conformance/test_ssf_transmitter.py @@ -0,0 +1,49 @@ +from authentik.core.models import Application +from authentik.crypto.models import CertificateKeyPair +from authentik.enterprise.providers.ssf.models import SSFProvider +from authentik.lib.generators import generate_id +from tests.decorators import retry +from tests.live import SSLLiveMixin +from tests.openid_conformance.base import TestOpenIDConformance + + +class TestOpenIDConformanceSSFTransmitter(TestOpenIDConformance, SSLLiveMixin): + + def setUp(self): + super().setUp() + self.provider = SSFProvider.objects.create( + name=generate_id(), + signing_key=CertificateKeyPair.objects.get(name="authentik Self-signed Certificate"), + backchannel_application=Application.objects.get(slug="oidc-conformance-1"), + push_verify_certificates=False, + ) + + @retry() + def test_openid_ssf_transmitter_test_plan(self): + iss = self.url( + "authentik_providers_ssf:configuration", + application_slug="oidc-conformance-1", + ) + self.run_test( + "openid-ssf-transmitter-test-plan", + { + "alias": "authentik", + "description": "authentik", + "ssf": { + "transmitter": { + "issuer": iss, + "configuration_metadata_endpoint": iss, + "access_token": self.provider.token.key, + } + }, + }, + test_variant={ + "client_auth_type": "client_secret_post", + "ssf_server_metadata": "static", + "server_metadata": "static", + "ssf_auth_mode": "static", + "ssf_delivery_mode": "push", + "ssf_profile": "caep_interop", + "client_registration": "static_client", + }, + )