providers/saml: migrate import to API, add API tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
4e3701ca8d
commit
5eb9b95ab5
|
@ -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 a new issue