Files
authentik/authentik/providers/radius/models.py
Dominic R 420c3e36bc alpha4
2026-08-30 21:17:59 -04:00

126 lines
4.4 KiB
Python

"""Radius Provider"""
from collections.abc import Iterable
from django.db import models, transaction
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.core.models import PropertyMapping, Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.generators import generate_id
from authentik.outposts.models import OutpostModel
from authentik.secrets.models import create_named_secret
class RadiusProvider(OutpostModel, Provider):
"""Allow applications to authenticate against authentik's users using Radius."""
secret = models.ForeignKey(
"authentik_secrets.Secret",
verbose_name=_("Shared Secret"),
help_text=_("Shared secret between clients and server to hash packets."),
on_delete=models.PROTECT,
null=True,
blank=True,
default=None,
related_name="radius_providers",
)
_shared_secret = models.TextField(
default=generate_id,
help_text=_("Shared secret between clients and server to hash packets."),
db_column="shared_secret",
)
client_networks = models.TextField(
default="0.0.0.0/0, ::/0",
help_text=_(
"List of CIDRs (comma-separated) that clients can connect from. A more specific "
"CIDR will match before a looser one. Clients connecting from a non-specified CIDR "
"will be dropped."
),
)
mfa_support = models.BooleanField(
default=True,
verbose_name="MFA Support",
help_text=_(
"When enabled, code-based multi-factor authentication can be used by appending a "
"semicolon and the TOTP code to the password. This should only be enabled if all "
"users that will bind to this provider have a TOTP device configured, as otherwise "
"a password may incorrectly be rejected if it contains a semicolon."
),
)
certificate = models.ForeignKey(
CertificateKeyPair, on_delete=models.CASCADE, default=None, null=True
)
@property
def launch_url(self) -> str | None:
"""Radius never has a launch URL"""
return None
def save(self, *args, **kwargs):
with transaction.atomic():
if not self.secret_id:
self.secret = create_named_secret(f"{self.name} shared secret")
if (update_fields := kwargs.get("update_fields")) is not None:
kwargs["update_fields"] = set(update_fields) | {"secret"}
return super().save(*args, **kwargs)
@property
def component(self) -> str:
return "ak-provider-radius-form"
@property
def icon_url(self) -> str | None:
return static("authentik/sources/radius.svg")
@property
def serializer(self) -> type[Serializer]:
from authentik.providers.radius.api.providers import RadiusProviderSerializer
return RadiusProviderSerializer
def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
required = [self, "authentik_stages_mtls.pass_outpost_certificate"]
if self.certificate is not None:
required.append(("authentik_crypto.view_certificatekeypair", self.certificate))
required.append(
("authentik_crypto.view_certificatekeypair_certificate", self.certificate)
)
required.append(("authentik_crypto.view_certificatekeypair_key", self.certificate))
return required
def __str__(self):
return f"Radius Provider {self.name}"
class Meta:
verbose_name = _("Radius Provider")
verbose_name_plural = _("Radius Providers")
class RadiusProviderPropertyMapping(PropertyMapping):
"""Add additional attributes to Radius authentication responses."""
@property
def component(self) -> str:
return "ak-property-mapping-provider-radius-form"
@property
def serializer(self) -> type[Serializer]:
from authentik.providers.radius.api.property_mappings import (
RadiusProviderPropertyMappingSerializer,
)
return RadiusProviderPropertyMappingSerializer
def __str__(self):
return f"Radius Provider Property Mapping {self.name}"
class Meta:
verbose_name = _("Radius Provider Property Mapping")
verbose_name_plural = _("Radius Provider Property Mappings")