diff --git a/authentik/blueprints/management/commands/apply_blueprint.py b/authentik/blueprints/management/commands/apply_blueprint.py index 698ff4b5c0..b996c5ebae 100644 --- a/authentik/blueprints/management/commands/apply_blueprint.py +++ b/authentik/blueprints/management/commands/apply_blueprint.py @@ -23,9 +23,11 @@ class Command(BaseCommand): for blueprint_path in options.get("blueprints", []): content = BlueprintInstance(path=blueprint_path).retrieve() importer = Importer.from_string(content) - valid, _ = importer.validate() + valid, logs = importer.validate() if not valid: - self.stderr.write("blueprint invalid") + self.stderr.write("Blueprint invalid") + for log in logs: + self.stderr.write(f"\t{log.logger}: {log.event}: {log.attributes}") sys_exit(1) importer.apply() diff --git a/authentik/lib/sync/mapper.py b/authentik/lib/sync/mapper.py index 1a287ee3c4..b7174a2927 100644 --- a/authentik/lib/sync/mapper.py +++ b/authentik/lib/sync/mapper.py @@ -20,6 +20,10 @@ class PropertyMappingManager: _evaluators: list[PropertyMappingEvaluator] + globals: dict + + __has_compiled: bool + def __init__( self, qs: QuerySet[PropertyMapping], @@ -33,7 +37,8 @@ class PropertyMappingManager: self.query_set = qs self.mapping_subclass = mapping_subclass self.context_keys = context_keys - self.compile() + self.globals = {} + self.__has_compiled = False def compile(self): self._evaluators = [] @@ -43,6 +48,7 @@ class PropertyMappingManager: evaluator = PropertyMappingEvaluator( mapping, **{key: None for key in self.context_keys} ) + evaluator._globals.update(self.globals) # Compile and cache expression evaluator.compile() self._evaluators.append(evaluator) @@ -56,6 +62,9 @@ class PropertyMappingManager: ) -> Generator[tuple[dict, PropertyMapping], None]: """Iterate over all mappings that were pre-compiled and execute all of them with the given context""" + if not self.__has_compiled: + self.compile() + self.__has_compiled = True for mapping in self._evaluators: mapping.set_context(user, request, **kwargs) try: diff --git a/authentik/providers/ldap/api.py b/authentik/providers/ldap/api.py index ddbb7c11c5..3a535be810 100644 --- a/authentik/providers/ldap/api.py +++ b/authentik/providers/ldap/api.py @@ -5,7 +5,8 @@ from django.db.models.query import Q from django_filters.filters import BooleanFilter from django_filters.filterset import FilterSet from rest_framework.fields import CharField, ListField, SerializerMethodField -from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet +from rest_framework.mixins import ListModelMixin +from rest_framework.viewsets import GenericViewSet, ModelViewSet from authentik.core.api.providers import ProviderSerializer from authentik.core.api.used_by import UsedByMixin @@ -105,7 +106,7 @@ class LDAPOutpostConfigSerializer(ModelSerializer): ] -class LDAPOutpostConfigViewSet(ReadOnlyModelViewSet): +class LDAPOutpostConfigViewSet(ListModelMixin, GenericViewSet): """LDAPProvider Viewset""" queryset = LDAPProvider.objects.filter( diff --git a/authentik/providers/proxy/api.py b/authentik/providers/proxy/api.py index aed400aba2..88ff9fe01e 100644 --- a/authentik/providers/proxy/api.py +++ b/authentik/providers/proxy/api.py @@ -6,7 +6,8 @@ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema_field from rest_framework.exceptions import ValidationError from rest_framework.fields import CharField, ListField, ReadOnlyField, SerializerMethodField -from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet +from rest_framework.mixins import ListModelMixin +from rest_framework.viewsets import GenericViewSet, ModelViewSet from authentik.core.api.providers import ProviderSerializer from authentik.core.api.used_by import UsedByMixin @@ -181,7 +182,7 @@ class ProxyOutpostConfigSerializer(ModelSerializer): ] -class ProxyOutpostConfigViewSet(ReadOnlyModelViewSet): +class ProxyOutpostConfigViewSet(ListModelMixin, GenericViewSet): """ProxyProvider Viewset""" queryset = ProxyProvider.objects.filter(application__isnull=False) diff --git a/authentik/providers/radius/api.py b/authentik/providers/radius/api.py deleted file mode 100644 index f32e90c1f5..0000000000 --- a/authentik/providers/radius/api.py +++ /dev/null @@ -1,71 +0,0 @@ -"""RadiusProvider API Views""" - -from rest_framework.fields import CharField, ListField -from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet - -from authentik.core.api.providers import ProviderSerializer -from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import ModelSerializer -from authentik.providers.radius.models import RadiusProvider - - -class RadiusProviderSerializer(ProviderSerializer): - """RadiusProvider Serializer""" - - outpost_set = ListField(child=CharField(), read_only=True, source="outpost_set.all") - - class Meta: - model = RadiusProvider - fields = ProviderSerializer.Meta.fields + [ - "client_networks", - # Shared secret is not a write-only field, as - # an admin might have to view it - "shared_secret", - "outpost_set", - "mfa_support", - ] - extra_kwargs = ProviderSerializer.Meta.extra_kwargs - - -class RadiusProviderViewSet(UsedByMixin, ModelViewSet): - """RadiusProvider Viewset""" - - queryset = RadiusProvider.objects.all() - serializer_class = RadiusProviderSerializer - ordering = ["name"] - search_fields = ["name", "client_networks"] - filterset_fields = { - "application": ["isnull"], - "name": ["iexact"], - "authorization_flow__slug": ["iexact"], - "client_networks": ["iexact"], - } - - -class RadiusOutpostConfigSerializer(ModelSerializer): - """RadiusProvider Serializer""" - - application_slug = CharField(source="application.slug") - auth_flow_slug = CharField(source="authorization_flow.slug") - - class Meta: - model = RadiusProvider - fields = [ - "pk", - "name", - "application_slug", - "auth_flow_slug", - "client_networks", - "shared_secret", - "mfa_support", - ] - - -class RadiusOutpostConfigViewSet(ReadOnlyModelViewSet): - """RadiusProvider Viewset""" - - queryset = RadiusProvider.objects.filter(application__isnull=False) - serializer_class = RadiusOutpostConfigSerializer - ordering = ["name"] - search_fields = ["name"] - filterset_fields = ["name"] diff --git a/authentik/providers/radius/api/__init__.py b/authentik/providers/radius/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/authentik/providers/radius/api/property_mappings.py b/authentik/providers/radius/api/property_mappings.py new file mode 100644 index 0000000000..b8bfaefff6 --- /dev/null +++ b/authentik/providers/radius/api/property_mappings.py @@ -0,0 +1,39 @@ +"""Radius Property mappings API Views""" + +from django_filters.filters import AllValuesMultipleFilter +from django_filters.filterset import FilterSet +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema_field +from rest_framework.viewsets import ModelViewSet + +from authentik.core.api.property_mappings import PropertyMappingSerializer +from authentik.core.api.used_by import UsedByMixin +from authentik.providers.radius.models import RadiusProviderPropertyMapping + + +class RadiusProviderPropertyMappingSerializer(PropertyMappingSerializer): + """RadiusProviderPropertyMapping Serializer""" + + class Meta: + model = RadiusProviderPropertyMapping + fields = PropertyMappingSerializer.Meta.fields + + +class RadiusProviderPropertyMappingFilter(FilterSet): + """Filter for RadiusProviderPropertyMapping""" + + managed = extend_schema_field(OpenApiTypes.STR)(AllValuesMultipleFilter(field_name="managed")) + + class Meta: + model = RadiusProviderPropertyMapping + fields = "__all__" + + +class RadiusProviderPropertyMappingViewSet(UsedByMixin, ModelViewSet): + """RadiusProviderPropertyMapping Viewset""" + + queryset = RadiusProviderPropertyMapping.objects.all() + serializer_class = RadiusProviderPropertyMappingSerializer + filterset_class = RadiusProviderPropertyMappingFilter + search_fields = ["name"] + ordering = ["name"] diff --git a/authentik/providers/radius/api/providers.py b/authentik/providers/radius/api/providers.py new file mode 100644 index 0000000000..9ded0acbc3 --- /dev/null +++ b/authentik/providers/radius/api/providers.py @@ -0,0 +1,166 @@ +"""RadiusProvider API Views""" + +from base64 import b64encode + +from django.shortcuts import get_object_or_404 +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter, extend_schema +from pyrad.dictionary import Attribute, Dictionary +from pyrad.packet import AuthPacket +from rest_framework.decorators import action +from rest_framework.fields import CharField, ListField +from rest_framework.mixins import ListModelMixin +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet, ModelViewSet + +from authentik.core.api.providers import ProviderSerializer +from authentik.core.api.used_by import UsedByMixin +from authentik.core.api.utils import ModelSerializer, PassiveSerializer +from authentik.core.expression.exceptions import PropertyMappingExpressionException +from authentik.core.models import Application +from authentik.events.models import Event, EventAction +from authentik.lib.expression.exceptions import ControlFlowException +from authentik.lib.sync.mapper import PropertyMappingManager +from authentik.lib.utils.errors import exception_to_string +from authentik.policies.api.exec import PolicyTestResultSerializer +from authentik.policies.engine import PolicyEngine +from authentik.policies.types import PolicyResult +from authentik.providers.radius.models import RadiusProvider, RadiusProviderPropertyMapping + + +class RadiusProviderSerializer(ProviderSerializer): + """RadiusProvider Serializer""" + + outpost_set = ListField(child=CharField(), read_only=True, source="outpost_set.all") + + class Meta: + model = RadiusProvider + fields = ProviderSerializer.Meta.fields + [ + "client_networks", + # Shared secret is not a write-only field, as + # an admin might have to view it + "shared_secret", + "outpost_set", + "mfa_support", + ] + extra_kwargs = ProviderSerializer.Meta.extra_kwargs + + +class RadiusProviderViewSet(UsedByMixin, ModelViewSet): + """RadiusProvider Viewset""" + + queryset = RadiusProvider.objects.all() + serializer_class = RadiusProviderSerializer + ordering = ["name"] + search_fields = ["name", "client_networks"] + filterset_fields = { + "application": ["isnull"], + "name": ["iexact"], + "authorization_flow__slug": ["iexact"], + "client_networks": ["iexact"], + } + + +class RadiusOutpostConfigSerializer(ModelSerializer): + """RadiusProvider Serializer""" + + application_slug = CharField(source="application.slug") + auth_flow_slug = CharField(source="authorization_flow.slug") + + class Meta: + model = RadiusProvider + fields = [ + "pk", + "name", + "application_slug", + "auth_flow_slug", + "client_networks", + "shared_secret", + "mfa_support", + ] + + +class RadiusOutpostConfigViewSet(ListModelMixin, GenericViewSet): + """RadiusProvider Viewset""" + + queryset = RadiusProvider.objects.filter(application__isnull=False) + serializer_class = RadiusOutpostConfigSerializer + ordering = ["name"] + search_fields = ["name"] + filterset_fields = ["name"] + + class RadiusCheckAccessSerializer(PassiveSerializer): + attributes = CharField(required=False) + access = PolicyTestResultSerializer() + + def get_attributes(self, provider: RadiusProvider): + mapper = PropertyMappingManager( + provider.property_mappings.all().order_by("name").select_subclasses(), + RadiusProviderPropertyMapping, + ["packet"], + ) + dict = Dictionary("authentik/providers/radius/dictionaries/dictionary") + + packet = AuthPacket() + packet.secret = provider.shared_secret + packet.dict = dict + + def define_attribute( + vendor_code: int, + vendor_name: str, + attribute_name: str, + attribute_code: int, + attribute_type: str, + ): + """Dynamically add attribute to Radius packet""" + # Ensure the vendor exists + if vendor_code not in dict.vendors.backward or vendor_name not in dict.vendors.forward: + dict.vendors.Add(vendor_name, vendor_code) + if attribute_name not in dict.attributes: + dict.attributes[f"{vendor_name}-{attribute_name}"] = Attribute( + attribute_name, attribute_code, attribute_type, vendor=vendor_name + ) + + mapper.globals["define_attribute"] = define_attribute + + try: + for _ in mapper.iter_eval(self.request.user, self.request, packet=packet): + pass + except (PropertyMappingExpressionException, ControlFlowException) as exc: + # Value error can be raised when assigning invalid data to an attribute + Event.new( + EventAction.CONFIGURATION_ERROR, + message=f"Failed to evaluate property-mapping {exception_to_string(exc)}", + mapping=exc.mapping, + ).save() + return None + return b64encode(packet.RequestPacket()).decode() + + @extend_schema( + request=None, + parameters=[OpenApiParameter("app_slug", OpenApiTypes.STR)], + responses={ + 200: RadiusCheckAccessSerializer(), + }, + ) + @action(detail=True) + def check_access(self, request: Request, pk) -> Response: + """Check access to a single application by slug""" + provider = get_object_or_404(RadiusProvider, pk=pk) + application = get_object_or_404(Application, slug=request.query_params["app_slug"]) + engine = PolicyEngine(application, request.user, request) + engine.use_cache = False + engine.build() + result = engine.result + access_response = PolicyResult(result.passing) + attributes = None + if result.passing: + attributes = self.get_attributes(provider) + response = self.RadiusCheckAccessSerializer( + instance={ + "attributes": attributes, + "access": access_response, + } + ) + return Response(response.data) diff --git a/tests/radius-dictionary b/authentik/providers/radius/dictionaries/dictionary similarity index 98% rename from tests/radius-dictionary rename to authentik/providers/radius/dictionaries/dictionary index badb1e3aba..e771bf31bf 100644 --- a/tests/radius-dictionary +++ b/authentik/providers/radius/dictionaries/dictionary @@ -36,11 +36,13 @@ # directive to the end of the file if you want to see the old names # in the logfiles too. # -#$INCLUDE dictionary.compat # compability issues +#$INCLUDE dictionary.compat # compatibility issues #$INCLUDE dictionary.acc #$INCLUDE dictionary.ascend #$INCLUDE dictionary.bay -#$INCLUDE dictionary.cisco +#$INCLUDE dictionary.aruba +$INCLUDE dictionary.cisco +$INCLUDE dictionary.microsoft #$INCLUDE dictionary.livingston #$INCLUDE dictionary.microsoft #$INCLUDE dictionary.quintum diff --git a/authentik/providers/radius/dictionaries/dictionary.aruba b/authentik/providers/radius/dictionaries/dictionary.aruba new file mode 100644 index 0000000000..3207f8f0a4 --- /dev/null +++ b/authentik/providers/radius/dictionaries/dictionary.aruba @@ -0,0 +1,132 @@ +# -*- text -*- +# Copyright (C) 2023 The FreeRADIUS Server project and contributors +# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0 +# Version $Id$ +# +# Version: $Id$ +# +VENDOR Aruba 14823 +BEGIN-VENDOR Aruba + +ATTRIBUTE User-Role 1 string +ATTRIBUTE User-Vlan 2 integer +ATTRIBUTE Priv-Admin-User 3 integer +ATTRIBUTE Admin-Role 4 string +ATTRIBUTE Essid-Name 5 string +ATTRIBUTE Location-Id 6 string +ATTRIBUTE Port-Identifier 7 string +ATTRIBUTE MMS-User-Template 8 string +ATTRIBUTE Named-User-Vlan 9 string +ATTRIBUTE AP-Group 10 string + +ATTRIBUTE Framed-IPv6-Address 11 string +ATTRIBUTE Device-Type 12 string +ATTRIBUTE No-DHCP-Fingerprint 14 integer +ATTRIBUTE Mdps-Device-Udid 15 string +ATTRIBUTE Mdps-Device-Imei 16 string +ATTRIBUTE Mdps-Device-Iccid 17 string +ATTRIBUTE Mdps-Max-Devices 18 integer +ATTRIBUTE Mdps-Device-Name 19 string +ATTRIBUTE Mdps-Device-Product 20 string + +ATTRIBUTE Mdps-Device-Version 21 string +ATTRIBUTE Mdps-Device-Serial 22 string +ATTRIBUTE CPPM-Role 23 string +ATTRIBUTE AirGroup-User-Name 24 string +ATTRIBUTE AirGroup-Shared-User 25 string +ATTRIBUTE AirGroup-Shared-Role 26 string +ATTRIBUTE AirGroup-Device-Type 27 integer +ATTRIBUTE Auth-Survivability 28 string +ATTRIBUTE AS-User-Name 29 string +ATTRIBUTE AS-Credential-Hash 30 string + +ATTRIBUTE WorkSpace-App-Name 31 string +ATTRIBUTE Mdps-Provisioning-Settings 32 string +ATTRIBUTE Mdps-Device-Profile 33 string +ATTRIBUTE AP-IP-Address 34 ipaddr +ATTRIBUTE AirGroup-Shared-Group 35 string +ATTRIBUTE User-Group 36 string +ATTRIBUTE Network-SSO-Token 37 string +ATTRIBUTE AirGroup-Version 38 integer +ATTRIBUTE Auth-SurvMethod 39 integer +ATTRIBUTE Port-Bounce-Host 40 integer + +ATTRIBUTE Calea-Server-Ip 41 ipaddr +ATTRIBUTE Admin-Path 42 string +ATTRIBUTE Captive-Portal-URL 43 string +ATTRIBUTE MPSK-Passphrase 44 octets encrypt=2 +ATTRIBUTE ACL-Server-Query-Info 45 string +ATTRIBUTE Command-String 46 string +ATTRIBUTE Network-Profile 47 string +ATTRIBUTE Admin-Device-Group 48 string +ATTRIBUTE PoE-Priority 49 integer +ATTRIBUTE Port-Auth-Mode 50 integer + +ATTRIBUTE NAS-Filter-Rule 51 string +ATTRIBUTE QoS-Trust-Mode 52 integer +ATTRIBUTE UBT-Gateway-Role 53 string +ATTRIBUTE Gateway-Zone 54 string + +ATTRIBUTE DPP-Bootstrapping-Key-SHA256 55 string +ATTRIBUTE DPP-Bootstrapping-Net-Access-Key-SHA256 56 string +ATTRIBUTE DPP-Bootstrapping-Key-B64 57 string + +ATTRIBUTE STP-Admin-Edge-Port 58 integer + +ATTRIBUTE UBT-Gateway-CPPM-Role 59 string + +ATTRIBUTE AP-MAC-Address 60 string +ATTRIBUTE Device-MAC-Address 61 string + +ATTRIBUTE MPSK-Key-Name 62 string + +ATTRIBUTE Device-Traffic-Class 63 integer + +ATTRIBUTE PVLAN-Port-Type 64 integer +ATTRIBUTE Network-Test 65 integer +ATTRIBUTE MPSK-Lookup-Info 66 string encrypt=1 +ATTRIBUTE AVPair 67 string +ATTRIBUTE DPP-Service-Type 68 integer + +ATTRIBUTE User-Mgmt-Interface 69 string + +ATTRIBUTE Poe-Allocate-By-Method 70 integer + +ATTRIBUTE DPP-AKMs 71 string +ATTRIBUTE DPP-Passphrase 72 string encrypt=2 + +VALUE AirGroup-Device-Type Personal-Device 1 +VALUE AirGroup-Device-Type Shared-Device 2 +VALUE AirGroup-Device-Type Deleted-Device 3 + +VALUE AirGroup-Version AirGroup-v1 1 +VALUE AirGroup-Version AirGroup-v2 2 + +VALUE PoE-Priority Critical 0 +VALUE PoE-Priority High 1 +VALUE PoE-Priority Low 2 + +VALUE Port-Auth-Mode Infrastructure-Mode 1 +VALUE Port-Auth-Mode Client-Mode 2 +VALUE Port-Auth-Mode Multi-Domain-Mode 3 + +VALUE QoS-Trust-Mode DSCP 0 +VALUE QoS-Trust-Mode QoS 1 +VALUE QoS-Trust-Mode None 2 + +VALUE STP-Admin-Edge-Port Disable 0 +VALUE STP-Admin-Edge-Port Enable 1 + +VALUE PVLAN-Port-Type None 0 +VALUE PVLAN-Port-Type Promiscuous 1 +VALUE PVLAN-Port-Type Secondary 2 + +VALUE DPP-Service-Type DDP-Boostrap-Authorization 1 +VALUE DPP-Service-Type DDP-Identity-Update 2 +VALUE DPP-Service-Type DDP-Net-Access 3 + +VALUE PoE-Allocate-By-Method Class 1 +VALUE PoE-Allocate-By-Method Usage 2 + +END-VENDOR Aruba +ALIAS Aruba Vendor-Specific.Aruba diff --git a/authentik/providers/radius/dictionaries/dictionary.cisco b/authentik/providers/radius/dictionaries/dictionary.cisco new file mode 100644 index 0000000000..94d5d66c02 --- /dev/null +++ b/authentik/providers/radius/dictionaries/dictionary.cisco @@ -0,0 +1,231 @@ +# -*- text -*- +# Copyright (C) 2023 The FreeRADIUS Server project and contributors +# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0 +# Version $Id$ +# +# dictionary.cisco +# +# Accounting VSAs originally by +# "Marcelo M. Sosa Lugones" +# +# Version: $Id$ +# +# For documentation on Cisco RADIUS attributes, see: +# +# http://www.cisco.com/univercd/cc/td/doc/product/access/acs_serv/vapp_dev/vsaig3.htm +# +# For general documentation on Cisco RADIUS configuration, see: +# +# http://www.cisco.com/en/US/partner/tech/tk583/tk547/tsd_technology_support_sub-protocol_home.html +# + +VENDOR Cisco 9 + +# +# Standard attribute +# +BEGIN-VENDOR Cisco + +ATTRIBUTE AVPair 1 string +ATTRIBUTE NAS-Port 2 string + +# +# T.37 Store-and-Forward attributes. +# +ATTRIBUTE Fax-Account-Id-Origin 3 string +ATTRIBUTE Fax-Msg-Id 4 string +ATTRIBUTE Fax-Pages 5 string +ATTRIBUTE Fax-Coverpage-Flag 6 string +ATTRIBUTE Fax-Modem-Time 7 string +ATTRIBUTE Fax-Connect-Speed 8 string +ATTRIBUTE Fax-Recipient-Count 9 string +ATTRIBUTE Fax-Process-Abort-Flag 10 string +ATTRIBUTE Fax-Dsn-Address 11 string +ATTRIBUTE Fax-Dsn-Flag 12 string +ATTRIBUTE Fax-Mdn-Address 13 string +ATTRIBUTE Fax-Mdn-Flag 14 string +ATTRIBUTE Fax-Auth-Status 15 string +ATTRIBUTE Email-Server-Address 16 string +ATTRIBUTE Email-Server-Ack-Flag 17 string +ATTRIBUTE Gateway-Id 18 string +ATTRIBUTE Call-Type 19 string +ATTRIBUTE Port-Used 20 string +ATTRIBUTE Abort-Cause 21 string + +# +# Voice over IP attributes. +# +ATTRIBUTE h323-remote-address 23 string +ATTRIBUTE h323-conf-id 24 string +ATTRIBUTE h323-setup-time 25 string +ATTRIBUTE h323-call-origin 26 string +ATTRIBUTE h323-call-type 27 string +ATTRIBUTE h323-connect-time 28 string +ATTRIBUTE h323-disconnect-time 29 string +ATTRIBUTE h323-disconnect-cause 30 string +ATTRIBUTE h323-voice-quality 31 string +ATTRIBUTE h323-gw-id 33 string +ATTRIBUTE h323-incoming-conf-id 35 string + +ATTRIBUTE Policy-Up 37 string +ATTRIBUTE Policy-Down 38 string + +ATTRIBUTE Relay-Information-Option 46 string +ATTRIBUTE DHCP-User-Class 47 string +ATTRIBUTE DHCP-Vendor-Class 48 string + +ATTRIBUTE DHCP-Relay-GiAddr 50 string +ATTRIBUTE Service-Name 51 string +ATTRIBUTE Parent-Session-Id 52 string + +ATTRIBUTE Sub-QoS-Pol-In 55 string +ATTRIBUTE Sub-QoS-Pol-Out 56 string +ATTRIBUTE In-ACL 57 string +ATTRIBUTE Out-ACL 58 string +ATTRIBUTE Sub-PBR-Policy-In 59 string +ATTRIBUTE Sub-Activate-Service 60 string +ATTRIBUTE IPv6-In-ACL 61 string +ATTRIBUTE IPv6-Out-ACL 62 string +ATTRIBUTE Sub-Deactivate-Service 63 string + +ATTRIBUTE DHCP-Subscriber-Id 65 string +ATTRIBUTE DHCPv6-Link-Address 66 string + +ATTRIBUTE sip-conf-id 100 string +ATTRIBUTE h323-credit-amount 101 string +ATTRIBUTE h323-credit-time 102 string +ATTRIBUTE h323-return-code 103 string +ATTRIBUTE h323-prompt-id 104 string +ATTRIBUTE h323-time-and-day 105 string +ATTRIBUTE h323-redirect-number 106 string +ATTRIBUTE h323-preferred-lang 107 string +ATTRIBUTE h323-redirect-ip-address 108 string +ATTRIBUTE h323-billing-model 109 string +ATTRIBUTE h323-currency 110 string +ATTRIBUTE subscriber 111 string +ATTRIBUTE gw-rxd-cdn 112 string +ATTRIBUTE gw-final-xlated-cdn 113 string +ATTRIBUTE remote-media-address 114 string +ATTRIBUTE release-source 115 string +ATTRIBUTE gw-rxd-cgn 116 string +ATTRIBUTE gw-final-xlated-cgn 117 string + +# SIP Attributes +ATTRIBUTE call-id 141 string +ATTRIBUTE session-protocol 142 string +ATTRIBUTE method 143 string +ATTRIBUTE prev-hop-via 144 string +ATTRIBUTE prev-hop-ip 145 string +ATTRIBUTE incoming-req-uri 146 string +ATTRIBUTE outgoing-req-uri 147 string +ATTRIBUTE next-hop-ip 148 string +ATTRIBUTE next-hop-dn 149 string +ATTRIBUTE sip-hdr 150 string +ATTRIBUTE dsp-id 151 string + +# +# Extra attributes sent by the Cisco, if you configure +# "radius-server vsa accounting" (requires IOS11.2+). +# +ATTRIBUTE Multilink-ID 187 integer +ATTRIBUTE Num-In-Multilink 188 integer +ATTRIBUTE Pre-Input-Octets 190 integer +ATTRIBUTE Pre-Output-Octets 191 integer +ATTRIBUTE Pre-Input-Packets 192 integer +ATTRIBUTE Pre-Output-Packets 193 integer +ATTRIBUTE Maximum-Time 194 integer +ATTRIBUTE Disconnect-Cause 195 integer +ATTRIBUTE Data-Rate 197 integer +ATTRIBUTE PreSession-Time 198 integer +ATTRIBUTE PW-Lifetime 208 integer +ATTRIBUTE IP-Direct 209 integer +ATTRIBUTE PPP-VJ-Slot-Comp 210 integer +ATTRIBUTE PPP-Async-Map 212 integer +ATTRIBUTE IP-Pool-Definition 217 string +ATTRIBUTE Assign-IP-Pool 218 integer +ATTRIBUTE Route-IP 228 integer +ATTRIBUTE Link-Compression 233 integer +ATTRIBUTE Target-Util 234 integer +ATTRIBUTE Maximum-Channels 235 integer +ATTRIBUTE Data-Filter 242 integer +ATTRIBUTE Call-Filter 243 integer +ATTRIBUTE Idle-Limit 244 integer +ATTRIBUTE Subscriber-Password 249 string +ATTRIBUTE Account-Info 250 string +ATTRIBUTE Service-Info 251 string +ATTRIBUTE Command-Code 252 string +ATTRIBUTE Control-Info 253 string +ATTRIBUTE Xmit-Rate 255 integer + +VALUE Disconnect-Cause No-Reason 0 +VALUE Disconnect-Cause No-Disconnect 1 +VALUE Disconnect-Cause Unknown 2 +VALUE Disconnect-Cause Call-Disconnect 3 +VALUE Disconnect-Cause CLID-Authentication-Failure 4 +VALUE Disconnect-Cause No-Modem-Available 9 +VALUE Disconnect-Cause No-Carrier 10 +VALUE Disconnect-Cause Lost-Carrier 11 +VALUE Disconnect-Cause No-Detected-Result-Codes 12 +VALUE Disconnect-Cause User-Ends-Session 20 +VALUE Disconnect-Cause Idle-Timeout 21 +VALUE Disconnect-Cause Exit-Telnet-Session 22 +VALUE Disconnect-Cause No-Remote-IP-Addr 23 +VALUE Disconnect-Cause Exit-Raw-TCP 24 +VALUE Disconnect-Cause Password-Fail 25 +VALUE Disconnect-Cause Raw-TCP-Disabled 26 +VALUE Disconnect-Cause Control-C-Detected 27 +VALUE Disconnect-Cause EXEC-Program-Destroyed 28 +VALUE Disconnect-Cause Close-Virtual-Connection 29 +VALUE Disconnect-Cause End-Virtual-Connection 30 +VALUE Disconnect-Cause Exit-Rlogin 31 +VALUE Disconnect-Cause Invalid-Rlogin-Option 32 +VALUE Disconnect-Cause Insufficient-Resources 33 +VALUE Disconnect-Cause Timeout-PPP-LCP 40 +VALUE Disconnect-Cause Failed-PPP-LCP-Negotiation 41 +VALUE Disconnect-Cause Failed-PPP-PAP-Auth-Fail 42 +VALUE Disconnect-Cause Failed-PPP-CHAP-Auth 43 +VALUE Disconnect-Cause Failed-PPP-Remote-Auth 44 +VALUE Disconnect-Cause PPP-Remote-Terminate 45 +VALUE Disconnect-Cause PPP-Closed-Event 46 +VALUE Disconnect-Cause NCP-Closed-PPP 47 +VALUE Disconnect-Cause MP-Error-PPP 48 +VALUE Disconnect-Cause PPP-Maximum-Channels 49 +VALUE Disconnect-Cause Tables-Full 50 +VALUE Disconnect-Cause Resources-Full 51 +VALUE Disconnect-Cause Invalid-IP-Address 52 +VALUE Disconnect-Cause Bad-Hostname 53 +VALUE Disconnect-Cause Bad-Port 54 +VALUE Disconnect-Cause Reset-TCP 60 +VALUE Disconnect-Cause TCP-Connection-Refused 61 +VALUE Disconnect-Cause Timeout-TCP 62 +VALUE Disconnect-Cause Foreign-Host-Close-TCP 63 +VALUE Disconnect-Cause TCP-Network-Unreachable 64 +VALUE Disconnect-Cause TCP-Host-Unreachable 65 +VALUE Disconnect-Cause TCP-Network-Admin-Unreachable 66 +VALUE Disconnect-Cause TCP-Port-Unreachable 67 +VALUE Disconnect-Cause Session-Timeout 100 +VALUE Disconnect-Cause Session-Failed-Security 101 +VALUE Disconnect-Cause Session-End-Callback 102 +VALUE Disconnect-Cause Invalid-Protocol 120 +VALUE Disconnect-Cause RADIUS-Disconnect 150 +VALUE Disconnect-Cause Local-Admin-Disconnect 151 +VALUE Disconnect-Cause SNMP-Disconnect 152 +VALUE Disconnect-Cause V110-Retries 160 +VALUE Disconnect-Cause PPP-Authentication-Timeout 170 +VALUE Disconnect-Cause Local-Hangup 180 +VALUE Disconnect-Cause Remote-Hangup 185 +VALUE Disconnect-Cause T1-Quiesced 190 +VALUE Disconnect-Cause Call-Duration 195 +VALUE Disconnect-Cause VPN-User-Disconnect 600 +VALUE Disconnect-Cause VPN-Carrier-Loss 601 +VALUE Disconnect-Cause VPN-No-Resources 602 +VALUE Disconnect-Cause VPN-Bad-Control-Packet 603 +VALUE Disconnect-Cause VPN-Admin-Disconnect 604 +VALUE Disconnect-Cause VPN-Tunnel-Shut 605 +VALUE Disconnect-Cause VPN-Local-Disconnect 606 +VALUE Disconnect-Cause VPN-Session-Limit 607 +VALUE Disconnect-Cause VPN-Call-Redirect 608 + +END-VENDOR Cisco + +ALIAS Cisco Vendor-Specific.Cisco diff --git a/authentik/providers/radius/dictionaries/dictionary.microsoft b/authentik/providers/radius/dictionaries/dictionary.microsoft new file mode 100644 index 0000000000..cb74ca2ad3 --- /dev/null +++ b/authentik/providers/radius/dictionaries/dictionary.microsoft @@ -0,0 +1,174 @@ +# -*- text -*- +# Copyright (C) 2023 The FreeRADIUS Server project and contributors +# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0 +# Version $Id$ +# +# Microsoft's VSA's, from RFC 2548 +# +# $Id$ +# + +VENDOR Microsoft 311 + +BEGIN-VENDOR Microsoft +ATTRIBUTE CHAP-Response 1 octets[50] +ATTRIBUTE CHAP-Error 2 string +ATTRIBUTE CHAP-CPW-1 3 octets[70] +ATTRIBUTE CHAP-CPW-2 4 octets[84] +ATTRIBUTE CHAP-LM-Enc-PW 5 octets +ATTRIBUTE CHAP-NT-Enc-PW 6 octets +ATTRIBUTE MPPE-Encryption-Policy 7 integer + +VALUE MPPE-Encryption-Policy Encryption-Allowed 1 +VALUE MPPE-Encryption-Policy Encryption-Required 2 + +# This is referred to as both singular and plural in the RFC. +# Plural seems to make more sense. +ATTRIBUTE MPPE-Encryption-Type 8 integer +ATTRIBUTE MPPE-Encryption-Types 8 integer + +VALUE MPPE-Encryption-Types RC4-40bit-Allowed 1 +VALUE MPPE-Encryption-Types RC4-128bit-Allowed 2 +VALUE MPPE-Encryption-Types RC4-40or128-bit-Allowed 6 + +ATTRIBUTE RAS-Vendor 9 integer # content is Vendor-ID +ATTRIBUTE CHAP-Domain 10 string +ATTRIBUTE CHAP-Challenge 11 octets +ATTRIBUTE CHAP-MPPE-Keys 12 octets[24] encrypt=1 +ATTRIBUTE BAP-Usage 13 integer +ATTRIBUTE Link-Utilization-Threshold 14 integer # values are 1-100 +ATTRIBUTE Link-Drop-Time-Limit 15 integer +ATTRIBUTE MPPE-Send-Key 16 octets encrypt=2 +ATTRIBUTE MPPE-Recv-Key 17 octets encrypt=2 +ATTRIBUTE RAS-Version 18 string +ATTRIBUTE Old-ARAP-Password 19 octets +ATTRIBUTE New-ARAP-Password 20 octets +ATTRIBUTE ARAP-PW-Change-Reason 21 integer + +ATTRIBUTE Filter 22 octets +ATTRIBUTE Acct-Auth-Type 23 integer +ATTRIBUTE Acct-EAP-Type 24 integer + +ATTRIBUTE CHAP2-Response 25 octets[50] +ATTRIBUTE CHAP2-Success 26 octets +ATTRIBUTE CHAP2-CPW 27 octets[68] + +ATTRIBUTE Primary-DNS-Server 28 ipaddr +ATTRIBUTE Secondary-DNS-Server 29 ipaddr +ATTRIBUTE Primary-NBNS-Server 30 ipaddr +ATTRIBUTE Secondary-NBNS-Server 31 ipaddr + +#ATTRIBUTE ARAP-Challenge 33 octets[8] + +## RNAP +# +# http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/%5BRNAP%5D.pdf + +ATTRIBUTE RAS-Client-Name 34 string +ATTRIBUTE RAS-Client-Version 35 string +ATTRIBUTE Quarantine-IPFilter 36 octets +ATTRIBUTE Quarantine-Session-Timeout 37 integer +ATTRIBUTE User-Security-Identity 40 string +ATTRIBUTE Identity-Type 41 integer +ATTRIBUTE Service-Class 42 string +ATTRIBUTE Quarantine-User-Class 44 string +ATTRIBUTE Quarantine-State 45 integer +ATTRIBUTE Quarantine-Grace-Time 46 integer +ATTRIBUTE Network-Access-Server-Type 47 integer +ATTRIBUTE AFW-Zone 48 integer + +VALUE AFW-Zone AFW-Zone-Boundary-Policy 1 +VALUE AFW-Zone AFW-Zone-Unprotected-Policy 2 +VALUE AFW-Zone AFW-Zone-Protected-Policy 3 + +ATTRIBUTE AFW-Protection-Level 49 integer + +VALUE AFW-Protection-Level HECP-Response-Sign-Only 1 +VALUE AFW-Protection-Level HECP-Response-Sign-And-Encrypt 2 + +ATTRIBUTE Machine-Name 50 string +ATTRIBUTE IPv6-Filter 51 octets +ATTRIBUTE IPv4-Remediation-Servers 52 octets +ATTRIBUTE IPv6-Remediation-Servers 53 octets +ATTRIBUTE RNAP-Not-Quarantine-Capable 54 integer + +VALUE RNAP-Not-Quarantine-Capable SoH-Sent 0 +VALUE RNAP-Not-Quarantine-Capable SoH-Not-Sent 1 + +ATTRIBUTE Quarantine-SOH 55 octets +ATTRIBUTE RAS-Correlation 56 octets + +# Or this might be 56? +ATTRIBUTE Extended-Quarantine-State 57 integer + +ATTRIBUTE HCAP-User-Groups 58 string +ATTRIBUTE HCAP-Location-Group-Name 59 string +ATTRIBUTE HCAP-User-Name 60 string +ATTRIBUTE User-IPv4-Address 61 ipaddr +ATTRIBUTE User-IPv6-Address 62 ipv6addr +ATTRIBUTE TSG-Device-Redirection 63 integer + +# +# Integer Translations +# + +# BAP-Usage Values + +VALUE BAP-Usage Not-Allowed 0 +VALUE BAP-Usage Allowed 1 +VALUE BAP-Usage Required 2 + +# ARAP-Password-Change-Reason Values + +VALUE ARAP-PW-Change-Reason Just-Change-Password 1 +VALUE ARAP-PW-Change-Reason Expired-Password 2 +VALUE ARAP-PW-Change-Reason Admin-Requires-Password-Change 3 +VALUE ARAP-PW-Change-Reason Password-Too-Short 4 + +# Acct-Auth-Type Values + +VALUE Acct-Auth-Type PAP 1 +VALUE Acct-Auth-Type CHAP 2 +VALUE Acct-Auth-Type CHAP-1 3 +VALUE Acct-Auth-Type CHAP-2 4 +VALUE Acct-Auth-Type EAP 5 + +# Acct-EAP-Type Values + +VALUE Acct-EAP-Type MD5 4 +VALUE Acct-EAP-Type OTP 5 +VALUE Acct-EAP-Type Generic-Token-Card 6 +VALUE Acct-EAP-Type TLS 13 + +# Identity-Type Values + +VALUE Identity-Type Machine-Health-Check 1 +VALUE Identity-Type Ignore-User-Lookup-Failure 2 + +# Quarantine-State Values + +VALUE Quarantine-State Full-Access 0 +VALUE Quarantine-State Quarantine 1 +VALUE Quarantine-State Probation 2 + +# Network-Access-Server-Type Values + +VALUE Network-Access-Server-Type Unspecified 0 +VALUE Network-Access-Server-Type Terminal-Server-Gateway 1 +VALUE Network-Access-Server-Type Remote-Access-Server 2 +VALUE Network-Access-Server-Type DHCP-Server 3 +VALUE Network-Access-Server-Type Wireless-Access-Point 4 +VALUE Network-Access-Server-Type HRA 5 +VALUE Network-Access-Server-Type HCAP-Server 6 + +# Extended-Quarantine-State Values + +VALUE Extended-Quarantine-State Transition 1 +VALUE Extended-Quarantine-State Infected 2 +VALUE Extended-Quarantine-State Unknown 3 +VALUE Extended-Quarantine-State No-Data 4 + +END-VENDOR Microsoft + +ALIAS Microsoft Vendor-Specific.Microsoft + diff --git a/authentik/providers/radius/migrations/0003_radiusproviderpropertymapping.py b/authentik/providers/radius/migrations/0003_radiusproviderpropertymapping.py new file mode 100644 index 0000000000..8f39dd83d0 --- /dev/null +++ b/authentik/providers/radius/migrations/0003_radiusproviderpropertymapping.py @@ -0,0 +1,36 @@ +# Generated by Django 5.0.7 on 2024-07-17 10:01 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_core", "0035_alter_group_options_and_more"), + ("authentik_providers_radius", "0002_radiusprovider_mfa_support"), + ] + + operations = [ + migrations.CreateModel( + name="RadiusProviderPropertyMapping", + fields=[ + ( + "propertymapping_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="authentik_core.propertymapping", + ), + ), + ], + options={ + "verbose_name": "Radius Property Mapping", + "verbose_name_plural": "Radius Property Mappings", + }, + bases=("authentik_core.propertymapping",), + ), + ] diff --git a/authentik/providers/radius/models.py b/authentik/providers/radius/models.py index 7e9784f191..1efa1213eb 100644 --- a/authentik/providers/radius/models.py +++ b/authentik/providers/radius/models.py @@ -5,7 +5,7 @@ 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 Provider +from authentik.core.models import PropertyMapping, Provider from authentik.lib.generators import generate_id from authentik.outposts.models import OutpostModel @@ -53,7 +53,7 @@ class RadiusProvider(OutpostModel, Provider): @property def serializer(self) -> type[Serializer]: - from authentik.providers.radius.api import RadiusProviderSerializer + from authentik.providers.radius.api.providers import RadiusProviderSerializer return RadiusProviderSerializer @@ -63,3 +63,25 @@ class RadiusProvider(OutpostModel, Provider): class Meta: verbose_name = _("Radius Provider") verbose_name_plural = _("Radius Providers") + + +class RadiusProviderPropertyMapping(PropertyMapping): + + @property + def component(self) -> str: + return "ak-property-mapping-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 Property Mapping {self.name}" + + class Meta: + verbose_name = _("Radius Property Mapping") + verbose_name_plural = _("Radius Property Mappings") diff --git a/authentik/providers/radius/urls.py b/authentik/providers/radius/urls.py index 56e304b0cf..ce64d925c2 100644 --- a/authentik/providers/radius/urls.py +++ b/authentik/providers/radius/urls.py @@ -1,8 +1,13 @@ """API URLs""" -from authentik.providers.radius.api import RadiusOutpostConfigViewSet, RadiusProviderViewSet +from authentik.providers.radius.api.property_mappings import RadiusProviderPropertyMappingViewSet +from authentik.providers.radius.api.providers import ( + RadiusOutpostConfigViewSet, + RadiusProviderViewSet, +) api_urlpatterns = [ + ("propertymappings/radius", RadiusProviderPropertyMappingViewSet), ("outposts/radius", RadiusOutpostConfigViewSet, "radiusprovideroutpost"), ("providers/radius", RadiusProviderViewSet), ] diff --git a/blueprints/schema.json b/blueprints/schema.json index d25ae77ce7..67e04ab206 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -744,6 +744,43 @@ } } }, + { + "type": "object", + "required": [ + "model", + "identifiers" + ], + "properties": { + "model": { + "const": "authentik_providers_radius.radiusproviderpropertymapping" + }, + "id": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "absent", + "present", + "created", + "must_created" + ], + "default": "present" + }, + "conditions": { + "type": "array", + "items": { + "type": "boolean" + } + }, + "attrs": { + "$ref": "#/$defs/model_authentik_providers_radius.radiusproviderpropertymapping" + }, + "identifiers": { + "$ref": "#/$defs/model_authentik_providers_radius.radiusproviderpropertymapping" + } + } + }, { "type": "object", "required": [ @@ -3520,6 +3557,7 @@ "authentik_providers_oauth2.oauth2provider", "authentik_providers_proxy.proxyprovider", "authentik_providers_radius.radiusprovider", + "authentik_providers_radius.radiusproviderpropertymapping", "authentik_providers_saml.samlprovider", "authentik_providers_saml.samlpropertymapping", "authentik_providers_scim.scimprovider", @@ -4199,6 +4237,31 @@ }, "required": [] }, + "model_authentik_providers_radius.radiusproviderpropertymapping": { + "type": "object", + "properties": { + "managed": { + "type": [ + "string", + "null" + ], + "minLength": 1, + "title": "Managed by authentik", + "description": "Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update." + }, + "name": { + "type": "string", + "minLength": 1, + "title": "Name" + }, + "expression": { + "type": "string", + "minLength": 1, + "title": "Expression" + } + }, + "required": [] + }, "model_authentik_providers_saml.samlprovider": { "type": "object", "properties": { diff --git a/internal/outpost/radius/api.go b/internal/outpost/radius/api.go index ce35e70961..ab6f275abc 100644 --- a/internal/outpost/radius/api.go +++ b/internal/outpost/radius/api.go @@ -46,6 +46,7 @@ func (rs *RadiusServer) Refresh() error { MFASupport: provider.GetMfaSupport(), appSlug: provider.ApplicationSlug, flowSlug: provider.AuthFlowSlug, + providerId: provider.Pk, s: rs, log: logger, } diff --git a/internal/outpost/radius/handle_access_request.go b/internal/outpost/radius/handle_access_request.go index 49f1775965..58f880cd78 100644 --- a/internal/outpost/radius/handle_access_request.go +++ b/internal/outpost/radius/handle_access_request.go @@ -1,6 +1,8 @@ package radius import ( + "encoding/base64" + "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" "goauthentik.io/internal/outpost/flow" @@ -43,7 +45,7 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR _ = w.Write(r.Response(radius.CodeAccessReject)) return } - access, err := fe.CheckApplicationAccess(r.pi.appSlug) + access, _, err := fe.ApiClient().OutpostsApi.OutpostsRadiusCheckAccessRetrieve(r.Context(), r.pi.providerId).AppSlug(r.pi.appSlug).Execute() if err != nil { r.Log().WithField("username", username).WithError(err).Warning("failed to check access") _ = w.Write(r.Response(radius.CodeAccessReject)) @@ -54,7 +56,7 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR }).Inc() return } - if !access { + if !access.Access.Passing { r.Log().WithField("username", username).Info("Access denied for user") _ = w.Write(r.Response(radius.CodeAccessReject)) metrics.RequestsRejected.With(prometheus.Labels{ @@ -64,5 +66,22 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR }).Inc() return } - _ = w.Write(r.Response(radius.CodeAccessAccept)) + res := r.Response(radius.CodeAccessAccept) + defer func() { _ = w.Write(res) }() + if !access.HasAttributes() { + r.Log().Debug("No attributes") + return + } + rawData, err := base64.StdEncoding.DecodeString(access.GetAttributes()) + if err != nil { + r.Log().WithError(err).Warning("failed to decode attributes from core") + return + } + p, err := radius.Parse(rawData, r.pi.SharedSecret) + if err != nil { + r.Log().WithError(err).Warning("failed to parse attributes from core") + } + for _, attr := range p.Attributes { + res.Add(attr.Type, attr.Attribute) + } } diff --git a/internal/outpost/radius/radius.go b/internal/outpost/radius/radius.go index 808336e575..2e98da0e6c 100644 --- a/internal/outpost/radius/radius.go +++ b/internal/outpost/radius/radius.go @@ -19,10 +19,11 @@ type ProviderInstance struct { SharedSecret []byte MFASupport bool - appSlug string - flowSlug string - s *RadiusServer - log *log.Entry + appSlug string + flowSlug string + providerId int32 + s *RadiusServer + log *log.Entry } type RadiusServer struct { diff --git a/schema.yml b/schema.yml index 9d2a308f1e..67fa9a0761 100644 --- a/schema.yml +++ b/schema.yml @@ -9637,40 +9637,6 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' - /outposts/ldap/{id}/: - get: - operationId: outposts_ldap_retrieve - description: LDAPProvider Viewset - parameters: - - in: path - name: id - schema: - type: integer - description: A unique integer value identifying this LDAP Provider. - required: true - tags: - - outposts - security: - - authentik: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/LDAPOutpostConfig' - description: '' - '400': - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationError' - description: '' - '403': - content: - application/json: - schema: - $ref: '#/components/schemas/GenericError' - description: '' /outposts/proxy/: get: operationId: outposts_proxy_list @@ -9727,40 +9693,6 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' - /outposts/proxy/{id}/: - get: - operationId: outposts_proxy_retrieve - description: ProxyProvider Viewset - parameters: - - in: path - name: id - schema: - type: integer - description: A unique integer value identifying this Proxy Provider. - required: true - tags: - - outposts - security: - - authentik: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/ProxyOutpostConfig' - description: '' - '400': - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationError' - description: '' - '403': - content: - application/json: - schema: - $ref: '#/components/schemas/GenericError' - description: '' /outposts/radius/: get: operationId: outposts_radius_list @@ -9817,11 +9749,15 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' - /outposts/radius/{id}/: + /outposts/radius/{id}/check_access/: get: - operationId: outposts_radius_retrieve - description: RadiusProvider Viewset + operationId: outposts_radius_check_access_retrieve + description: Check access to a single application by slug parameters: + - in: query + name: app_slug + schema: + type: string - in: path name: id schema: @@ -9837,7 +9773,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RadiusOutpostConfig' + $ref: '#/components/schemas/RadiusCheckAccess' description: '' '400': content: @@ -14591,6 +14527,292 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' + /propertymappings/radius/: + get: + operationId: propertymappings_radius_list + description: RadiusProviderPropertyMapping Viewset + parameters: + - in: query + name: expression + schema: + type: string + - in: query + name: managed + schema: + type: array + items: + type: string + explode: true + style: form + - in: query + name: name + schema: + type: string + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: pm_uuid + schema: + type: string + format: uuid + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - propertymappings + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRadiusProviderPropertyMappingList' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + post: + operationId: propertymappings_radius_create + description: RadiusProviderPropertyMapping Viewset + tags: + - propertymappings + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RadiusProviderPropertyMappingRequest' + required: true + security: + - authentik: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/RadiusProviderPropertyMapping' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /propertymappings/radius/{pm_uuid}/: + get: + operationId: propertymappings_radius_retrieve + description: RadiusProviderPropertyMapping Viewset + parameters: + - in: path + name: pm_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Radius Property Mapping. + required: true + tags: + - propertymappings + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RadiusProviderPropertyMapping' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + put: + operationId: propertymappings_radius_update + description: RadiusProviderPropertyMapping Viewset + parameters: + - in: path + name: pm_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Radius Property Mapping. + required: true + tags: + - propertymappings + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RadiusProviderPropertyMappingRequest' + required: true + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RadiusProviderPropertyMapping' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + patch: + operationId: propertymappings_radius_partial_update + description: RadiusProviderPropertyMapping Viewset + parameters: + - in: path + name: pm_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Radius Property Mapping. + required: true + tags: + - propertymappings + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedRadiusProviderPropertyMappingRequest' + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RadiusProviderPropertyMapping' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + delete: + operationId: propertymappings_radius_destroy + description: RadiusProviderPropertyMapping Viewset + parameters: + - in: path + name: pm_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Radius Property Mapping. + required: true + tags: + - propertymappings + security: + - authentik: [] + responses: + '204': + description: No response body + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /propertymappings/radius/{pm_uuid}/used_by/: + get: + operationId: propertymappings_radius_used_by_list + description: Get a list of all objects that use this object + parameters: + - in: path + name: pm_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Radius Property Mapping. + required: true + tags: + - propertymappings + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/UsedBy' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' /propertymappings/saml/: get: operationId: propertymappings_saml_list @@ -20798,6 +21020,7 @@ paths: - authentik_providers_rac.racpropertymapping - authentik_providers_rac.racprovider - authentik_providers_radius.radiusprovider + - authentik_providers_radius.radiusproviderpropertymapping - authentik_providers_saml.samlpropertymapping - authentik_providers_saml.samlprovider - authentik_providers_scim.scimmapping @@ -21017,6 +21240,7 @@ paths: - authentik_providers_rac.racpropertymapping - authentik_providers_rac.racprovider - authentik_providers_radius.radiusprovider + - authentik_providers_radius.radiusproviderpropertymapping - authentik_providers_saml.samlpropertymapping - authentik_providers_saml.samlprovider - authentik_providers_scim.scimmapping @@ -38602,6 +38826,7 @@ components: - authentik_providers_oauth2.oauth2provider - authentik_providers_proxy.proxyprovider - authentik_providers_radius.radiusprovider + - authentik_providers_radius.radiusproviderpropertymapping - authentik_providers_saml.samlprovider - authentik_providers_saml.samlpropertymapping - authentik_providers_scim.scimprovider @@ -40493,6 +40718,18 @@ components: required: - pagination - results + PaginatedRadiusProviderPropertyMappingList: + type: object + properties: + pagination: + $ref: '#/components/schemas/Pagination' + results: + type: array + items: + $ref: '#/components/schemas/RadiusProviderPropertyMapping' + required: + - pagination + - results PaginatedReputationList: type: object properties: @@ -43196,6 +43433,25 @@ components: delete_token_on_disconnect: type: boolean description: When set to true, connection tokens will be deleted upon disconnect. + PatchedRadiusProviderPropertyMappingRequest: + type: object + description: RadiusProviderPropertyMapping Serializer + properties: + managed: + type: string + nullable: true + minLength: 1 + title: Managed by authentik + description: Objects that are managed by authentik. These objects are created + and updated automatically. This flag only indicates that an object can + be overwritten by migrations. You can still modify the objects via the + API, but expect changes to be overwritten in a later update. + name: + type: string + minLength: 1 + expression: + type: string + minLength: 1 PatchedRadiusProviderRequest: type: object description: RadiusProvider Serializer @@ -45329,6 +45585,16 @@ components: required: - authorization_flow - name + RadiusCheckAccess: + type: object + description: Base serializer class which doesn't implement create/update methods + properties: + attributes: + type: string + access: + $ref: '#/components/schemas/PolicyTestResult' + required: + - access RadiusOutpostConfig: type: object description: RadiusProvider Serializer @@ -45453,6 +45719,73 @@ components: - pk - verbose_name - verbose_name_plural + RadiusProviderPropertyMapping: + type: object + description: RadiusProviderPropertyMapping Serializer + properties: + pk: + type: string + format: uuid + readOnly: true + title: Pm uuid + managed: + type: string + nullable: true + title: Managed by authentik + description: Objects that are managed by authentik. These objects are created + and updated automatically. This flag only indicates that an object can + be overwritten by migrations. You can still modify the objects via the + API, but expect changes to be overwritten in a later update. + name: + type: string + expression: + type: string + component: + type: string + description: Get object's component so that we know how to edit the object + readOnly: true + verbose_name: + type: string + description: Return object's verbose_name + readOnly: true + verbose_name_plural: + type: string + description: Return object's plural verbose_name + readOnly: true + meta_model_name: + type: string + description: Return internal model name + readOnly: true + required: + - component + - expression + - meta_model_name + - name + - pk + - verbose_name + - verbose_name_plural + RadiusProviderPropertyMappingRequest: + type: object + description: RadiusProviderPropertyMapping Serializer + properties: + managed: + type: string + nullable: true + minLength: 1 + title: Managed by authentik + description: Objects that are managed by authentik. These objects are created + and updated automatically. This flag only indicates that an object can + be overwritten by migrations. You can still modify the objects via the + API, but expect changes to be overwritten in a later update. + name: + type: string + minLength: 1 + expression: + type: string + minLength: 1 + required: + - expression + - name RadiusProviderRequest: type: object description: RadiusProvider Serializer diff --git a/tests/e2e/test_provider_radius.py b/tests/e2e/test_provider_radius.py index f67f6a1885..cbe3fbffea 100644 --- a/tests/e2e/test_provider_radius.py +++ b/tests/e2e/test_provider_radius.py @@ -87,7 +87,7 @@ class TestProviderRadius(SeleniumTestCase): srv = Client( server="localhost", secret=self.shared_secret.encode(), - dict=Dictionary("tests/radius-dictionary"), + dict=Dictionary("authentik/providers/radius/dictionaries/dictionary"), ) req = srv.CreateAuthPacket( @@ -109,7 +109,7 @@ class TestProviderRadius(SeleniumTestCase): srv = Client( server="localhost", secret=self.shared_secret.encode(), - dict=Dictionary("tests/radius-dictionary"), + dict=Dictionary("authentik/providers/radius/dictionaries/dictionary"), ) req = srv.CreateAuthPacket( diff --git a/web/src/admin/property-mappings/PropertyMappingListPage.ts b/web/src/admin/property-mappings/PropertyMappingListPage.ts index c884033d35..ca7bf87feb 100644 --- a/web/src/admin/property-mappings/PropertyMappingListPage.ts +++ b/web/src/admin/property-mappings/PropertyMappingListPage.ts @@ -3,6 +3,7 @@ import "@goauthentik/admin/property-mappings/PropertyMappingLDAPSourceForm"; import "@goauthentik/admin/property-mappings/PropertyMappingMicrosoftEntraForm"; import "@goauthentik/admin/property-mappings/PropertyMappingNotification"; import "@goauthentik/admin/property-mappings/PropertyMappingRACForm"; +import "@goauthentik/admin/property-mappings/PropertyMappingRadiusForm"; import "@goauthentik/admin/property-mappings/PropertyMappingSAMLForm"; import "@goauthentik/admin/property-mappings/PropertyMappingSCIMForm"; import "@goauthentik/admin/property-mappings/PropertyMappingScopeForm"; diff --git a/web/src/admin/property-mappings/PropertyMappingRadiusForm.ts b/web/src/admin/property-mappings/PropertyMappingRadiusForm.ts new file mode 100644 index 0000000000..e9b0b90f22 --- /dev/null +++ b/web/src/admin/property-mappings/PropertyMappingRadiusForm.ts @@ -0,0 +1,73 @@ +import { BasePropertyMappingForm } from "@goauthentik/admin/property-mappings/BasePropertyMappingForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { docLink } from "@goauthentik/common/global"; +import "@goauthentik/elements/CodeMirror"; +import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { PropertymappingsApi, RadiusProviderPropertyMapping } from "@goauthentik/api"; + +@customElement("ak-property-mapping-radius-form") +export class PropertyMappingRadiusForm extends BasePropertyMappingForm { + loadInstance(pk: string): Promise { + return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRadiusRetrieve({ + pmUuid: pk, + }); + } + + async send(data: RadiusProviderPropertyMapping): Promise { + if (this.instance) { + return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRadiusUpdate({ + pmUuid: this.instance.pk, + radiusProviderPropertyMappingRequest: data, + }); + } else { + return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRadiusCreate({ + radiusProviderPropertyMappingRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html` + + + + + +

+ ${msg("Expression using Python.")} + + ${msg("See documentation for a list of all variables.")} + +

+
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ak-property-mapping-radius-form": PropertyMappingRadiusForm; + } +} diff --git a/web/src/admin/providers/radius/RadiusProviderForm.ts b/web/src/admin/providers/radius/RadiusProviderForm.ts index 21a5173d59..b479a0c590 100644 --- a/web/src/admin/providers/radius/RadiusProviderForm.ts +++ b/web/src/admin/providers/radius/RadiusProviderForm.ts @@ -2,6 +2,7 @@ import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm" import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; +import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; @@ -11,7 +12,35 @@ import { TemplateResult, html } from "lit"; import { ifDefined } from "lit-html/directives/if-defined.js"; import { customElement } from "lit/decorators.js"; -import { FlowsInstancesListDesignationEnum, ProvidersApi, RadiusProvider } from "@goauthentik/api"; +import { + FlowsInstancesListDesignationEnum, + PropertymappingsApi, + ProvidersApi, + RadiusProvider, + RadiusProviderPropertyMapping, +} from "@goauthentik/api"; + +export async function radiusPropertyMappingsProvider(page = 1, search = "") { + const propertyMappings = await new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsRadiusList({ + ordering: "name", + pageSize: 20, + search: search.trim(), + page, + }); + return { + pagination: propertyMappings.pagination, + options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]), + }; +} + +export function makeRadiusPropertyMappingsSelector(instanceMappings?: string[]) { + const localMappings = instanceMappings ? new Set(instanceMappings) : undefined; + return localMappings + ? ([pk, _]: DualSelectPair) => localMappings.has(pk) + : ([_0, _1, _2, _]: DualSelectPair) => []; +} @customElement("ak-provider-radius-form") export class RadiusProviderFormPage extends WithBrandConfig(BaseProviderForm) { @@ -118,6 +147,22 @@ export class RadiusProviderFormPage extends WithBrandConfig(BaseProviderForm + + +

+ ${msg("Hold control/command to select multiple items.")} +

+
`; }