providers/saml: migrate import to API, add API tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		| @ -2,7 +2,6 @@ | ||||
| from django.urls import path | ||||
|  | ||||
| from authentik.admin.views import policies, providers, sources, stages | ||||
| from authentik.providers.saml.views.metadata import MetadataImportView | ||||
|  | ||||
| urlpatterns = [ | ||||
|     # Sources | ||||
| @ -25,11 +24,6 @@ urlpatterns = [ | ||||
|         providers.ProviderCreateView.as_view(), | ||||
|         name="provider-create", | ||||
|     ), | ||||
|     path( | ||||
|         "providers/create/saml/from-metadata/", | ||||
|         MetadataImportView.as_view(), | ||||
|         name="provider-saml-from-metadata", | ||||
|     ), | ||||
|     path( | ||||
|         "providers/<int:pk>/update/", | ||||
|         providers.ProviderUpdateView.as_view(), | ||||
|  | ||||
| @ -1,17 +1,33 @@ | ||||
| """SAMLProvider API Views""" | ||||
| from xml.etree.ElementTree import ParseError  # nosec | ||||
|  | ||||
| from defusedxml.ElementTree import fromstring | ||||
| from django.http.response import HttpResponse | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from drf_yasg.utils import swagger_auto_schema | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import ReadOnlyField | ||||
| from rest_framework.fields import CharField, FileField, ReadOnlyField | ||||
| from rest_framework.parsers import MultiPartParser | ||||
| from rest_framework.relations import SlugRelatedField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import Serializer | ||||
| from rest_framework.serializers import ValidationError | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.api.decorators import permission_required | ||||
| from authentik.core.api.propertymappings import PropertyMappingSerializer | ||||
| from authentik.core.api.providers import ProviderSerializer | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.core.models import Provider | ||||
| from authentik.flows.models import Flow, FlowDesignation | ||||
| from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider | ||||
| from authentik.providers.saml.views.metadata import DescriptorDownloadView | ||||
| from authentik.providers.saml.processors.metadata import MetadataProcessor | ||||
| from authentik.providers.saml.processors.metadata_parser import ( | ||||
|     ServiceProviderMetadataParser, | ||||
| ) | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class SAMLProviderSerializer(ProviderSerializer): | ||||
| @ -33,19 +49,26 @@ class SAMLProviderSerializer(ProviderSerializer): | ||||
|             "signature_algorithm", | ||||
|             "signing_kp", | ||||
|             "verification_kp", | ||||
|             "sp_binding", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class SAMLMetadataSerializer(Serializer): | ||||
| class SAMLMetadataSerializer(PassiveSerializer): | ||||
|     """SAML Provider Metadata serializer""" | ||||
|  | ||||
|     metadata = ReadOnlyField() | ||||
|  | ||||
|     def create(self, request: Request) -> Response: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, request: Request) -> Response: | ||||
|         raise NotImplementedError | ||||
| class SAMLProviderImportSerializer(PassiveSerializer): | ||||
|     """Import saml provider from XML Metadata""" | ||||
|  | ||||
|     name = CharField(required=True) | ||||
|     # Using SlugField because https://github.com/OpenAPITools/openapi-generator/issues/3278 | ||||
|     authorization_flow = SlugRelatedField( | ||||
|         queryset=Flow.objects.filter(designation=FlowDesignation.AUTHORIZATION), | ||||
|         slug_field="slug", | ||||
|     ) | ||||
|     file = FileField() | ||||
|  | ||||
|  | ||||
| class SAMLProviderViewSet(ModelViewSet): | ||||
| @ -61,11 +84,53 @@ class SAMLProviderViewSet(ModelViewSet): | ||||
|         """Return metadata as XML string""" | ||||
|         provider = self.get_object() | ||||
|         try: | ||||
|             metadata = DescriptorDownloadView.get_metadata(request, provider) | ||||
|             metadata = MetadataProcessor(provider, request).build_entity_descriptor() | ||||
|             if "download" in request._request.GET: | ||||
|                 response = HttpResponse(metadata, content_type="application/xml") | ||||
|                 response[ | ||||
|                     "Content-Disposition" | ||||
|                 ] = f'attachment; filename="{provider.name}_authentik_meta.xml"' | ||||
|                 return response | ||||
|             return Response({"metadata": metadata}) | ||||
|         except Provider.application.RelatedObjectDoesNotExist:  # pylint: disable=no-member | ||||
|             return Response({"metadata": ""}) | ||||
|  | ||||
|     @permission_required( | ||||
|         None, | ||||
|         [ | ||||
|             "authentik_providers_saml.add_samlprovider", | ||||
|             "authentik_crypto.add_certificatekeypair", | ||||
|         ], | ||||
|     ) | ||||
|     @swagger_auto_schema( | ||||
|         request_body=SAMLProviderImportSerializer(), | ||||
|         responses={204: "Successfully imported provider", 400: "Bad request"}, | ||||
|     ) | ||||
|     @action(detail=False, methods=["POST"], parser_classes=(MultiPartParser,)) | ||||
|     def import_metadata(self, request: Request) -> Response: | ||||
|         """Create provider from SAML Metadata""" | ||||
|         data = SAMLProviderImportSerializer(data=request.data) | ||||
|         if not data.is_valid(): | ||||
|             raise ValidationError(data.errors) | ||||
|         file = data.validated_data["file"] | ||||
|         # Validate syntax first | ||||
|         try: | ||||
|             fromstring(file.read()) | ||||
|         except ParseError: | ||||
|             raise ValidationError(_("Invalid XML Syntax")) | ||||
|         file.seek(0) | ||||
|         try: | ||||
|             metadata = ServiceProviderMetadataParser().parse(file.read().decode()) | ||||
|             metadata.to_provider( | ||||
|                 data.validated_data["name"], data.validated_data["authorization_flow"] | ||||
|             ) | ||||
|         except ValueError as exc:  # pragma: no cover | ||||
|             LOGGER.warning(str(exc)) | ||||
|             return ValidationError( | ||||
|                 _("Failed to import Metadata: %(message)s" % {"message": str(exc)}), | ||||
|             ) | ||||
|         return Response(status=204) | ||||
|  | ||||
|  | ||||
| class SAMLPropertyMappingSerializer(PropertyMappingSerializer): | ||||
|     """SAMLPropertyMapping Serializer""" | ||||
|  | ||||
| @ -1,78 +0,0 @@ | ||||
| """authentik SAML IDP Forms""" | ||||
|  | ||||
| from xml.etree.ElementTree import ParseError  # nosec | ||||
|  | ||||
| from defusedxml.ElementTree import fromstring | ||||
| from django import forms | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.core.validators import FileExtensionValidator | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.flows.models import Flow, FlowDesignation | ||||
| from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider | ||||
|  | ||||
|  | ||||
| class SAMLProviderForm(forms.ModelForm): | ||||
|     """SAML Provider form""" | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.fields["authorization_flow"].queryset = Flow.objects.filter( | ||||
|             designation=FlowDesignation.AUTHORIZATION | ||||
|         ) | ||||
|         self.fields["property_mappings"].queryset = SAMLPropertyMapping.objects.all() | ||||
|         self.fields["signing_kp"].queryset = CertificateKeyPair.objects.exclude( | ||||
|             key_data__iexact="" | ||||
|         ) | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = SAMLProvider | ||||
|         fields = [ | ||||
|             "name", | ||||
|             "authorization_flow", | ||||
|             "acs_url", | ||||
|             "issuer", | ||||
|             "sp_binding", | ||||
|             "audience", | ||||
|             "signing_kp", | ||||
|             "verification_kp", | ||||
|             "property_mappings", | ||||
|             "name_id_mapping", | ||||
|             "assertion_valid_not_before", | ||||
|             "assertion_valid_not_on_or_after", | ||||
|             "session_valid_not_on_or_after", | ||||
|             "digest_algorithm", | ||||
|             "signature_algorithm", | ||||
|         ] | ||||
|         widgets = { | ||||
|             "name": forms.TextInput(), | ||||
|             "audience": forms.TextInput(), | ||||
|             "issuer": forms.TextInput(), | ||||
|             "assertion_valid_not_before": forms.TextInput(), | ||||
|             "assertion_valid_not_on_or_after": forms.TextInput(), | ||||
|             "session_valid_not_on_or_after": forms.TextInput(), | ||||
|         } | ||||
|  | ||||
|  | ||||
| class SAMLProviderImportForm(forms.Form): | ||||
|     """Create a SAML Provider from SP Metadata.""" | ||||
|  | ||||
|     provider_name = forms.CharField() | ||||
|     authorization_flow = forms.ModelChoiceField( | ||||
|         queryset=Flow.objects.filter(designation=FlowDesignation.AUTHORIZATION) | ||||
|     ) | ||||
|     metadata = forms.FileField( | ||||
|         validators=[FileExtensionValidator(allowed_extensions=["xml"])] | ||||
|     ) | ||||
|  | ||||
|     def clean_metadata(self): | ||||
|         """Check if the flow is valid XML""" | ||||
|         metadata = self.cleaned_data["metadata"].read() | ||||
|         try: | ||||
|             fromstring(metadata) | ||||
|         except ParseError: | ||||
|             raise ValidationError(_("Invalid XML Syntax")) | ||||
|         self.cleaned_data["metadata"].seek(0) | ||||
|         return self.cleaned_data["metadata"] | ||||
| @ -3,7 +3,6 @@ from typing import Optional, Type | ||||
| from urllib.parse import urlparse | ||||
|  | ||||
| from django.db import models | ||||
| from django.forms import ModelForm | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from rest_framework.serializers import Serializer | ||||
| from structlog.stdlib import get_logger | ||||
| @ -171,10 +170,8 @@ class SAMLProvider(Provider): | ||||
|         return SAMLProviderSerializer | ||||
|  | ||||
|     @property | ||||
|     def form(self) -> Type[ModelForm]: | ||||
|         from authentik.providers.saml.forms import SAMLProviderForm | ||||
|  | ||||
|         return SAMLProviderForm | ||||
|     def component(self) -> str: | ||||
|         return "ak-provider-saml-form" | ||||
|  | ||||
|     def __str__(self): | ||||
|         return f"SAML Provider {self.name}" | ||||
|  | ||||
							
								
								
									
										115
									
								
								authentik/providers/saml/tests/test_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								authentik/providers/saml/tests/test_api.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| """SAML Provider API Tests""" | ||||
| from tempfile import TemporaryFile | ||||
|  | ||||
| from django.urls import reverse | ||||
| from rest_framework.test import APITestCase | ||||
|  | ||||
| from authentik.core.models import Application, User | ||||
| from authentik.flows.models import Flow, FlowDesignation | ||||
| from authentik.providers.saml.models import SAMLProvider | ||||
| from authentik.providers.saml.tests.test_metadata import METADATA_SIMPLE | ||||
|  | ||||
|  | ||||
| class TestSAMLProviderAPI(APITestCase): | ||||
|     """SAML Provider API Tests""" | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
|         super().setUp() | ||||
|         self.user = User.objects.get(username="akadmin") | ||||
|         self.client.force_login(self.user) | ||||
|  | ||||
|     def test_metadata(self): | ||||
|         """Test metadata export (normal)""" | ||||
|         provider = SAMLProvider.objects.create( | ||||
|             name="test", | ||||
|             authorization_flow=Flow.objects.get( | ||||
|                 slug="default-provider-authorization-implicit-consent" | ||||
|             ), | ||||
|         ) | ||||
|         Application.objects.create(name="test", provider=provider, slug="test") | ||||
|         response = self.client.get( | ||||
|             reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}), | ||||
|         ) | ||||
|         self.assertEqual(200, response.status_code) | ||||
|  | ||||
|     def test_metadata_download(self): | ||||
|         """Test metadata export (download)""" | ||||
|         provider = SAMLProvider.objects.create( | ||||
|             name="test", | ||||
|             authorization_flow=Flow.objects.get( | ||||
|                 slug="default-provider-authorization-implicit-consent" | ||||
|             ), | ||||
|         ) | ||||
|         Application.objects.create(name="test", provider=provider, slug="test") | ||||
|         response = self.client.get( | ||||
|             reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}) | ||||
|             + "?download", | ||||
|         ) | ||||
|         self.assertEqual(200, response.status_code) | ||||
|         self.assertIn("Content-Disposition", response) | ||||
|  | ||||
|     def test_metadata_invalid(self): | ||||
|         """Test metadata export (invalid)""" | ||||
|         # Provider without application | ||||
|         provider = SAMLProvider.objects.create( | ||||
|             name="test", | ||||
|             authorization_flow=Flow.objects.get( | ||||
|                 slug="default-provider-authorization-implicit-consent" | ||||
|             ), | ||||
|         ) | ||||
|         response = self.client.get( | ||||
|             reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}), | ||||
|         ) | ||||
|         self.assertEqual(200, response.status_code) | ||||
|  | ||||
|     def test_import_success(self): | ||||
|         """Test metadata import (success case)""" | ||||
|         with TemporaryFile() as metadata: | ||||
|             metadata.write(METADATA_SIMPLE.encode()) | ||||
|             metadata.seek(0) | ||||
|             response = self.client.post( | ||||
|                 reverse("authentik_api:samlprovider-import-metadata"), | ||||
|                 { | ||||
|                     "file": metadata, | ||||
|                     "name": "test", | ||||
|                     "authorization_flow": Flow.objects.filter( | ||||
|                         designation=FlowDesignation.AUTHORIZATION | ||||
|                     ) | ||||
|                     .first() | ||||
|                     .pk, | ||||
|                 }, | ||||
|                 format="multipart", | ||||
|             ) | ||||
|         self.assertEqual(204, response.status_code) | ||||
|         # We don't test the actual object being created here, that has its own tests | ||||
|  | ||||
|     def test_import_failed(self): | ||||
|         """Test metadata import (invalid xml)""" | ||||
|         with TemporaryFile() as metadata: | ||||
|             metadata.write(b"invalid") | ||||
|             metadata.seek(0) | ||||
|             response = self.client.post( | ||||
|                 reverse("authentik_api:samlprovider-import-metadata"), | ||||
|                 { | ||||
|                     "file": metadata, | ||||
|                     "name": "test", | ||||
|                     "authorization_flow": Flow.objects.filter( | ||||
|                         designation=FlowDesignation.AUTHORIZATION | ||||
|                     ) | ||||
|                     .first() | ||||
|                     .pk, | ||||
|                 }, | ||||
|                 format="multipart", | ||||
|             ) | ||||
|         self.assertEqual(400, response.status_code) | ||||
|  | ||||
|     def test_import_invalid(self): | ||||
|         """Test metadata import (invalid input)""" | ||||
|         response = self.client.post( | ||||
|             reverse("authentik_api:samlprovider-import-metadata"), | ||||
|             { | ||||
|                 "name": "test", | ||||
|             }, | ||||
|             format="multipart", | ||||
|         ) | ||||
|         self.assertEqual(400, response.status_code) | ||||
| @ -1,7 +1,7 @@ | ||||
| """authentik SAML IDP URLs""" | ||||
| from django.urls import path | ||||
|  | ||||
| from authentik.providers.saml.views import metadata, sso | ||||
| from authentik.providers.saml.views import sso | ||||
|  | ||||
| urlpatterns = [ | ||||
|     # SSO Bindings | ||||
| @ -21,9 +21,4 @@ urlpatterns = [ | ||||
|         sso.SAMLSSOBindingInitView.as_view(), | ||||
|         name="sso-init", | ||||
|     ), | ||||
|     path( | ||||
|         "<slug:application_slug>/metadata/", | ||||
|         metadata.DescriptorDownloadView.as_view(), | ||||
|         name="metadata", | ||||
|     ), | ||||
| ] | ||||
|  | ||||
| @ -1,81 +0,0 @@ | ||||
| """authentik SAML IDP Views""" | ||||
|  | ||||
| from django.contrib import messages | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.http import HttpRequest, HttpResponse | ||||
| from django.shortcuts import get_object_or_404 | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.views import View | ||||
| from django.views.generic.edit import FormView | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.models import Application, Provider | ||||
| from authentik.lib.views import bad_request_message | ||||
| from authentik.providers.saml.forms import SAMLProviderImportForm | ||||
| from authentik.providers.saml.models import SAMLProvider | ||||
| from authentik.providers.saml.processors.metadata import MetadataProcessor | ||||
| from authentik.providers.saml.processors.metadata_parser import ( | ||||
|     ServiceProviderMetadataParser, | ||||
| ) | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class DescriptorDownloadView(View): | ||||
|     """Replies with the XML Metadata IDSSODescriptor.""" | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_metadata(request: HttpRequest, provider: SAMLProvider) -> str: | ||||
|         """Return rendered XML Metadata""" | ||||
|         return MetadataProcessor(provider, request).build_entity_descriptor() | ||||
|  | ||||
|     def get(self, request: HttpRequest, application_slug: str) -> HttpResponse: | ||||
|         """Replies with the XML Metadata IDSSODescriptor.""" | ||||
|         application = get_object_or_404(Application, slug=application_slug) | ||||
|         provider: SAMLProvider = get_object_or_404( | ||||
|             SAMLProvider, pk=application.provider_id | ||||
|         ) | ||||
|         try: | ||||
|             metadata = DescriptorDownloadView.get_metadata(request, provider) | ||||
|         except Provider.application.RelatedObjectDoesNotExist:  # pylint: disable=no-member | ||||
|             return bad_request_message( | ||||
|                 request, "Provider is not assigned to an application." | ||||
|             ) | ||||
|         else: | ||||
|             response = HttpResponse(metadata, content_type="application/xml") | ||||
|             response[ | ||||
|                 "Content-Disposition" | ||||
|             ] = f'attachment; filename="{provider.name}_authentik_meta.xml"' | ||||
|             return response | ||||
|  | ||||
|  | ||||
| class MetadataImportView(LoginRequiredMixin, FormView): | ||||
|     """Import Metadata from XML, and create provider""" | ||||
|  | ||||
|     form_class = SAMLProviderImportForm | ||||
|     template_name = "providers/saml/import.html" | ||||
|     success_url = "/" | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         if not request.user.is_superuser: | ||||
|             return self.handle_no_permission() | ||||
|         return super().dispatch(request, *args, **kwargs) | ||||
|  | ||||
|     def form_valid(self, form: SAMLProviderImportForm) -> HttpResponse: | ||||
|         try: | ||||
|             metadata = ServiceProviderMetadataParser().parse( | ||||
|                 form.cleaned_data["metadata"].read().decode() | ||||
|             ) | ||||
|             metadata.to_provider( | ||||
|                 form.cleaned_data["provider_name"], | ||||
|                 form.cleaned_data["authorization_flow"], | ||||
|             ) | ||||
|             messages.success(self.request, _("Successfully created Provider")) | ||||
|         except ValueError as exc: | ||||
|             LOGGER.warning(str(exc)) | ||||
|             messages.error( | ||||
|                 self.request, | ||||
|                 _("Failed to import Metadata: %(message)s" % {"message": str(exc)}), | ||||
|             ) | ||||
|             return super().form_invalid(form) | ||||
|         return super().form_valid(form) | ||||
							
								
								
									
										36
									
								
								swagger.yaml
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								swagger.yaml
									
									
									
									
									
								
							| @ -9227,6 +9227,42 @@ paths: | ||||
|       tags: | ||||
|         - providers | ||||
|     parameters: [] | ||||
|   /providers/saml/import_metadata/: | ||||
|     post: | ||||
|       operationId: providers_saml_import_metadata | ||||
|       description: Create provider from SAML Metadata | ||||
|       parameters: | ||||
|         - name: name | ||||
|           in: formData | ||||
|           required: true | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|         - name: authorization_flow | ||||
|           in: formData | ||||
|           required: true | ||||
|           type: string | ||||
|           format: slug | ||||
|           pattern: ^[-a-zA-Z0-9_]+$ | ||||
|         - name: file | ||||
|           in: formData | ||||
|           required: true | ||||
|           type: file | ||||
|       responses: | ||||
|         '204': | ||||
|           description: Successfully imported provider | ||||
|         '400': | ||||
|           description: Invalid input. | ||||
|           schema: | ||||
|             $ref: '#/definitions/ValidationError' | ||||
|         '403': | ||||
|           description: Authentication credentials were invalid, absent or insufficient. | ||||
|           schema: | ||||
|             $ref: '#/definitions/GenericError' | ||||
|       consumes: | ||||
|         - multipart/form-data | ||||
|       tags: | ||||
|         - providers | ||||
|     parameters: [] | ||||
|   /providers/saml/{id}/: | ||||
|     get: | ||||
|       operationId: providers_saml_read | ||||
|  | ||||
| @ -12,6 +12,7 @@ const resources = [ | ||||
|  | ||||
|     { src: "node_modules/@patternfly/patternfly/patternfly-base.css", dest: "dist/" }, | ||||
|     { src: "node_modules/@patternfly/patternfly/patternfly.min.css", dest: "dist/" }, | ||||
|     { src: "node_modules/@patternfly/patternfly/patternfly.min.css.map", dest: "dist/" }, | ||||
|     { src: "src/authentik.css", dest: "dist/" }, | ||||
|  | ||||
|     { src: "node_modules/@patternfly/patternfly/assets/*", dest: "dist/assets/" }, | ||||
|  | ||||
| @ -22,9 +22,6 @@ export class AppURLManager { | ||||
|     static sourceOAuth(slug: string, action: string): string { | ||||
|         return `/source/oauth/${action}/${slug}/`; | ||||
|     } | ||||
|     static providerSAML(rest: string): string { | ||||
|         return `/application/saml/${rest}`; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -11,6 +11,7 @@ import "../../elements/forms/ProxyForm"; | ||||
| import "./oauth2/OAuth2ProviderForm"; | ||||
| import "./proxy/ProxyProviderForm"; | ||||
| import "./saml/SAMLProviderForm"; | ||||
| import "./saml/SAMLProviderImportForm"; | ||||
| import { TableColumn } from "../../elements/table/Table"; | ||||
| import { until } from "lit-html/directives/until"; | ||||
| import { PAGE_SIZE } from "../../constants"; | ||||
|  | ||||
							
								
								
									
										64
									
								
								web/src/pages/providers/saml/SAMLProviderImportForm.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								web/src/pages/providers/saml/SAMLProviderImportForm.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| import { FlowDesignationEnum, FlowsApi, ProvidersApi, SAMLProvider } from "authentik-api"; | ||||
| import { gettext } from "django"; | ||||
| import { customElement } from "lit-element"; | ||||
| import { html, TemplateResult } from "lit-html"; | ||||
| import { ifDefined } from "lit-html/directives/if-defined"; | ||||
| import { until } from "lit-html/directives/until"; | ||||
| import { DEFAULT_CONFIG } from "../../../api/Config"; | ||||
| import { Form } from "../../../elements/forms/Form"; | ||||
| import "../../../elements/forms/HorizontalFormElement"; | ||||
|  | ||||
| @customElement("ak-provider-saml-import-form") | ||||
| export class SAMLProviderImportForm extends Form<SAMLProvider> { | ||||
|  | ||||
|     getSuccessMessage(): string { | ||||
|         return gettext("Successfully imported provider."); | ||||
|     } | ||||
|  | ||||
|     // eslint-disable-next-line | ||||
|     send = (data: SAMLProvider): Promise<void> => { | ||||
|         const file = this.getFormFile(); | ||||
|         if (!file) { | ||||
|             throw new Error("No form data"); | ||||
|         } | ||||
|         return new ProvidersApi(DEFAULT_CONFIG).providersSamlImportMetadata({ | ||||
|             file: file, | ||||
|             name: data.name, | ||||
|             authorizationFlow: data.authorizationFlow, | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     renderForm(): TemplateResult { | ||||
|         return html`<form class="pf-c-form pf-m-horizontal"> | ||||
|             <ak-form-element-horizontal | ||||
|                 label=${gettext("Name")} | ||||
|                 ?required=${true} | ||||
|                 name="name"> | ||||
|                 <input type="text" class="pf-c-form-control" required> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal | ||||
|                 label=${gettext("Authorization flow")} | ||||
|                 ?required=${true} | ||||
|                 name="authorizationFlow"> | ||||
|                 <select class="pf-c-form-control"> | ||||
|                     ${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({ | ||||
|                         ordering: "pk", | ||||
|                         designation: FlowDesignationEnum.Authorization, | ||||
|                     }).then(flows => { | ||||
|                         return flows.results.map(flow => { | ||||
|                             return html`<option value=${ifDefined(flow.pk)}>${flow.name}</option>`; | ||||
|                         }); | ||||
|                     }))} | ||||
|                 </select> | ||||
|                 <p class="pf-c-form__helper-text">${gettext("Flow used when authorizing this provider.")}</p> | ||||
|             </ak-form-element-horizontal> | ||||
|  | ||||
|             <ak-form-element-horizontal | ||||
|                 label=${gettext("Metadata")} | ||||
|                 name="flow"> | ||||
|                 <input type="file" value="" class="pf-c-form-control"> | ||||
|             </ak-form-element-horizontal> | ||||
|         </form>`; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -12,6 +12,7 @@ import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; | ||||
| import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; | ||||
| import AKGlobal from "../../../authentik.css"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||
|  | ||||
| import "../../../elements/buttons/ModalButton"; | ||||
| import "../../../elements/buttons/SpinnerButton"; | ||||
| @ -23,7 +24,6 @@ import "./SAMLProviderForm"; | ||||
| import { Page } from "../../../elements/Page"; | ||||
| import { ProvidersApi, SAMLProvider } from "authentik-api"; | ||||
| import { DEFAULT_CONFIG } from "../../../api/Config"; | ||||
| import { AppURLManager } from "../../../api/legacy"; | ||||
| import { EVENT_REFRESH } from "../../../constants"; | ||||
| import { ifDefined } from "lit-html/directives/if-defined"; | ||||
|  | ||||
| @ -55,7 +55,7 @@ export class SAMLProviderViewPage extends Page { | ||||
|     provider?: SAMLProvider; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal]; | ||||
|         return [PFBase, PFPage, PFButton, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal]; | ||||
|     } | ||||
|  | ||||
|     constructor() { | ||||
| @ -153,27 +153,28 @@ export class SAMLProviderViewPage extends Page { | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </section> | ||||
|                 ${this.provider.assignedApplicationName ? html` | ||||
|                 <section slot="page-3" data-tab-title="${gettext("Metadata")}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||
|                     <div class="pf-u-display-flex pf-u-justify-content-center"> | ||||
|                         <div class="pf-u-w-75"> | ||||
|                             <div class="pf-c-card"> | ||||
|                                 <div class="pf-c-card__body"> | ||||
|                                     ${until( | ||||
|                                         new ProvidersApi(DEFAULT_CONFIG).providersSamlMetadata({ | ||||
|                                             id: this.provider.pk || 0, | ||||
|                                         }).then(m => { | ||||
|                                             return html`<ak-codemirror mode="xml" ?readOnly=${true} value="${ifDefined(m.metadata)}"></ak-codemirror>`; | ||||
|                                         }) | ||||
|                                     )} | ||||
|                                     ${until(new ProvidersApi(DEFAULT_CONFIG).providersSamlMetadata({ | ||||
|                                         id: this.provider.pk || 0, | ||||
|                                     }).then(m => { | ||||
|                                         return html`<ak-codemirror mode="xml" ?readOnly=${true} value="${ifDefined(m.metadata)}"></ak-codemirror>`; | ||||
|                                     }))} | ||||
|                                 </div> | ||||
|                                 <div class="pf-c-card__footer"> | ||||
|                                     <a class="pf-c-button pf-m-primary" target="_blank" href="${AppURLManager.providerSAML(`${this.provider.assignedApplicationSlug}/metadata/`)}"> | ||||
|                                     <a class="pf-c-button pf-m-primary" target="_blank" | ||||
|                                         href="/api/v2beta/providers/saml/${this.provider.pk}/metadata/?download"> | ||||
|                                         ${gettext("Download")} | ||||
|                                     </a> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 ` : html``} | ||||
|                 </section> | ||||
|             </ak-tabs>`; | ||||
|     } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer