diff --git a/authentik/providers/saml/api.py b/authentik/providers/saml/api.py index cf7ed1d75..475e1b0b0 100644 --- a/authentik/providers/saml/api.py +++ b/authentik/providers/saml/api.py @@ -57,10 +57,10 @@ class SAMLProviderViewSet(ModelViewSet): @swagger_auto_schema(responses={200: SAMLMetadataSerializer(many=False)}) @action(methods=["GET"], detail=True) - # pylint: disable=invalid-name + # pylint: disable=invalid-name, unused-argument def metadata(self, request: Request, pk: int) -> Response: """Return metadata as XML string""" - provider = get_object_or_404(SAMLProvider, pk=pk) + provider = self.get_object() try: metadata = DescriptorDownloadView.get_metadata(request, provider) return Response({"metadata": metadata}) diff --git a/authentik/sources/saml/api.py b/authentik/sources/saml/api.py index 0ed982dcf..d499ce20b 100644 --- a/authentik/sources/saml/api.py +++ b/authentik/sources/saml/api.py @@ -1,8 +1,14 @@ """SAMLSource API Views""" +from drf_yasg2.utils import swagger_auto_schema +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from authentik.core.api.sources import SourceSerializer +from authentik.providers.saml.api import SAMLMetadataSerializer from authentik.sources.saml.models import SAMLSource +from authentik.sources.saml.processors.metadata import MetadataProcessor class SAMLSourceSerializer(SourceSerializer): @@ -31,3 +37,12 @@ class SAMLSourceViewSet(ModelViewSet): queryset = SAMLSource.objects.all() serializer_class = SAMLSourceSerializer lookup_field = "slug" + + @swagger_auto_schema(responses={200: SAMLMetadataSerializer(many=False)}) + @action(methods=["GET"], detail=True) + # pylint: disable=unused-argument + def metadata(self, request: Request, slug: str) -> Response: + """Return metadata as XML string""" + source = self.get_object() + metadata = MetadataProcessor(source, request).build_entity_descriptor() + return Response({"metadata": metadata}) diff --git a/authentik/sources/saml/forms.py b/authentik/sources/saml/forms.py index b33ae455c..66801ec27 100644 --- a/authentik/sources/saml/forms.py +++ b/authentik/sources/saml/forms.py @@ -1,6 +1,7 @@ """authentik SAML SP Forms""" from django import forms +from django.utils.translation import gettext_lazy as _ from authentik.crypto.models import CertificateKeyPair from authentik.flows.models import Flow, FlowDesignation @@ -51,3 +52,7 @@ class SAMLSourceForm(forms.ModelForm): "slo_url": forms.TextInput(), "temporary_user_delete_after": forms.TextInput(), } + labels = { + "name_id_policy": _("Name ID Policy"), + "allow_idp_initiated": _("Allow IDP-initiated logins"), + } diff --git a/authentik/sources/saml/migrations/0009_auto_20210301_0949.py b/authentik/sources/saml/migrations/0009_auto_20210301_0949.py new file mode 100644 index 000000000..b52675c39 --- /dev/null +++ b/authentik/sources/saml/migrations/0009_auto_20210301_0949.py @@ -0,0 +1,40 @@ +# Generated by Django 3.1.7 on 2021-03-01 09:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_sources_saml", "0008_auto_20201112_2016"), + ] + + operations = [ + migrations.AlterField( + model_name="samlsource", + name="name_id_policy", + field=models.TextField( + choices=[ + ("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"), + ( + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + "Persistent", + ), + ( + "urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName", + "X509", + ), + ( + "urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName", + "Windows", + ), + ( + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", + "Transient", + ), + ], + default="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.", + ), + ), + ] diff --git a/authentik/sources/saml/models.py b/authentik/sources/saml/models.py index 007cc89fc..64fc897e8 100644 --- a/authentik/sources/saml/models.py +++ b/authentik/sources/saml/models.py @@ -79,7 +79,7 @@ class SAMLSource(Source): ) name_id_policy = models.TextField( choices=SAMLNameIDPolicy.choices, - default=SAMLNameIDPolicy.TRANSIENT, + default=SAMLNameIDPolicy.PERSISTENT, help_text=_( "NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent." ), diff --git a/authentik/sources/saml/processors/metadata.py b/authentik/sources/saml/processors/metadata.py index b379db67b..a0cd61a35 100644 --- a/authentik/sources/saml/processors/metadata.py +++ b/authentik/sources/saml/processors/metadata.py @@ -90,4 +90,4 @@ class MetadataProcessor: self.http_request ) - return tostring(entity_descriptor).decode() + return tostring(entity_descriptor, pretty_print=True).decode() diff --git a/authentik/stages/authenticator_validate/migrations/0004_auto_20210301_0949.py b/authentik/stages/authenticator_validate/migrations/0004_auto_20210301_0949.py new file mode 100644 index 000000000..4e5ca58e6 --- /dev/null +++ b/authentik/stages/authenticator_validate/migrations/0004_auto_20210301_0949.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.7 on 2021-03-01 09:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "authentik_stages_authenticator_validate", + "0003_authenticatorvalidatestage_device_classes", + ), + ] + + operations = [ + migrations.AlterField( + model_name="authenticatorvalidatestage", + name="not_configured_action", + field=models.TextField( + choices=[("skip", "Skip"), ("deny", "Deny")], default="skip" + ), + ), + ] diff --git a/swagger.yaml b/swagger.yaml index c9bfe0cf1..bbfdfac1b 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -5571,6 +5571,26 @@ paths: type: string format: slug pattern: ^[-a-zA-Z0-9_]+$ + /sources/saml/{slug}/metadata/: + get: + operationId: sources_saml_metadata + description: Return metadata as XML string + parameters: [] + responses: + '200': + description: SAML Provider Metadata serializer + schema: + $ref: '#/definitions/SAMLMetadata' + tags: + - sources + parameters: + - name: slug + in: path + description: Internal source name, used in URLs. + required: true + type: string + format: slug + pattern: ^[-a-zA-Z0-9_]+$ /stages/all/: get: operationId: stages_all_list