authentik.endpoints.connectors.agent.controller

  1from plistlib import PlistFormat, dumps
  2from uuid import uuid4
  3from xml.etree.ElementTree import Element, SubElement, tostring  # nosec
  4
  5from django.http import HttpRequest
  6from django.urls import reverse
  7from rest_framework.fields import CharField
  8
  9from authentik.core.api.utils import PassiveSerializer
 10from authentik.endpoints.connectors.agent.models import AgentConnector, EnrollmentToken
 11from authentik.endpoints.controller import BaseController, Capabilities
 12from authentik.endpoints.facts import OSFamily
 13
 14
 15def csp_create_replace_item(loc_uri, data_value) -> Element:
 16    """Create a Replace/Item element with the specified LocURI and Data"""
 17    replace = Element("Replace")
 18    item = SubElement(replace, "Item")
 19
 20    # Meta section
 21    meta = SubElement(item, "Meta")
 22    format_elem = SubElement(meta, "Format")
 23    format_elem.set("xmlns", "syncml:metinf")
 24    format_elem.text = "chr"
 25
 26    # Target section
 27    target = SubElement(item, "Target")
 28    loc_uri_elem = SubElement(target, "LocURI")
 29    loc_uri_elem.text = loc_uri
 30
 31    # Data section
 32    data = SubElement(item, "Data")
 33    data.text = data_value
 34
 35    return replace
 36
 37
 38class MDMConfigResponseSerializer(PassiveSerializer):
 39
 40    config = CharField(required=True)
 41    mime_type = CharField(required=True)
 42    filename = CharField(required=True)
 43
 44
 45class AgentConnectorController(BaseController[AgentConnector]):
 46
 47    @staticmethod
 48    def vendor_identifier() -> str:
 49        return "goauthentik.io/platform"
 50
 51    def capabilities(self) -> list[Capabilities]:
 52        return [Capabilities.STAGE_ENDPOINTS]
 53
 54    def generate_mdm_config(
 55        self, target_platform: OSFamily, request: HttpRequest, token: EnrollmentToken
 56    ) -> MDMConfigResponseSerializer:
 57        response = None
 58        if target_platform == OSFamily.windows:
 59            response = self._generate_mdm_config_windows(request, token)
 60        if target_platform in [OSFamily.iOS, OSFamily.macOS]:
 61            response = self._generate_mdm_config_macos(request, token)
 62        if not response:
 63            raise ValueError(f"Unsupported platform for MDM Configuration: {target_platform}")
 64        response.is_valid(raise_exception=True)
 65        return response
 66
 67    def _generate_mdm_config_windows(
 68        self, request: HttpRequest, token: EnrollmentToken
 69    ) -> MDMConfigResponseSerializer:
 70        base_uri = (
 71            "./Vendor/MSFT/Registry/HKLM/SOFTWARE/authentik Security Inc./Platform/ManagedConfig"
 72        )
 73        token_item = csp_create_replace_item(
 74            base_uri + "/RegistrationToken",
 75            token.key,
 76        )
 77        url_item = csp_create_replace_item(
 78            base_uri + "/URL",
 79            request.build_absolute_uri(reverse("authentik_core:root-redirect")),
 80        )
 81
 82        payload = tostring(token_item, encoding="unicode") + tostring(url_item, encoding="unicode")
 83        return MDMConfigResponseSerializer(
 84            data={
 85                "config": payload,
 86                "mime_type": "application/xml",
 87                "filename": f"{self.connector.name}_config.csp.xml",
 88            }
 89        )
 90
 91    def _generate_mdm_config_macos(
 92        self, request: HttpRequest, token: EnrollmentToken
 93    ) -> MDMConfigResponseSerializer:
 94        token_uuid = str(token.pk).upper()
 95        payload = dumps(
 96            {
 97                "PayloadContent": [
 98                    # Config for authentik Platform Agent (sysd)
 99                    {
100                        "PayloadDisplayName": "authentik Platform",
101                        "PayloadIdentifier": f"io.goauthentik.platform.{token_uuid}",
102                        "PayloadType": "io.goauthentik.platform",
103                        "PayloadUUID": str(uuid4()),
104                        "PayloadVersion": 1,
105                        "RegistrationToken": token.key,
106                        "URL": request.build_absolute_uri(reverse("authentik_core:root-redirect")),
107                    },
108                    # Config for MDM-associated domains (required for PSSO)
109                    {
110                        "PayloadDisplayName": "Associated Domains",
111                        "PayloadIdentifier": f"com.apple.associated-domains.{token_uuid}",
112                        "PayloadType": "com.apple.associated-domains",
113                        "PayloadUUID": str(uuid4()),
114                        "PayloadVersion": 1,
115                        "Configuration": [
116                            {
117                                "ApplicationIdentifier": "232G855Y8N.io.goauthentik.platform.agent",
118                                "AssociatedDomains": [f"authsrv:{request.get_host()}"],
119                                "EnableDirectDownloads": False,
120                            }
121                        ],
122                    },
123                    # Config for Platform SSO
124                    {
125                        "PayloadDisplayName": "Platform Single Sign-On",
126                        "PayloadIdentifier": f"com.apple.extensiblesso.{token_uuid}",
127                        "PayloadType": "com.apple.extensiblesso",
128                        "PayloadUUID": str(uuid4()),
129                        "PayloadVersion": 1,
130                        "ExtensionIdentifier": "io.goauthentik.platform.psso",
131                        "TeamIdentifier": "232G855Y8N",
132                        "Type": "Redirect",
133                        "URLs": [
134                            request.build_absolute_uri(reverse("authentik_core:root-redirect")),
135                        ],
136                        "PlatformSSO": {
137                            "AccountDisplayName": "authentik",
138                            "AllowDeviceIdentifiersInAttestation": True,
139                            "AuthenticationMethod": "UserSecureEnclaveKey",
140                            "EnableAuthorization": True,
141                            "EnableCreateUserAtLogin": True,
142                            "FileVaultPolicy": ["RequireAuthentication"],
143                            "LoginPolicy": ["RequireAuthentication"],
144                            "NewUserAuthorizationMode": "Standard",
145                            "UnlockPolicy": ["RequireAuthentication"],
146                            "UseSharedDeviceKeys": True,
147                            "UserAuthorizationMode": "Standard",
148                        },
149                    },
150                ],
151                "PayloadDisplayName": "authentik Platform",
152                "PayloadIdentifier": str(self.connector.pk).upper(),
153                "PayloadScope": "System",
154                "PayloadType": "Configuration",
155                "PayloadUUID": str(self.connector.pk).upper(),
156                "PayloadVersion": 1,
157            },
158            fmt=PlistFormat.FMT_XML,
159        ).decode()
160        return MDMConfigResponseSerializer(
161            data={
162                "config": payload,
163                "mime_type": "application/xml",
164                "filename": f"{self.connector.name}_config.mobileconfig",
165            }
166        )
def csp_create_replace_item(loc_uri, data_value) -> xml.etree.ElementTree.Element:
16def csp_create_replace_item(loc_uri, data_value) -> Element:
17    """Create a Replace/Item element with the specified LocURI and Data"""
18    replace = Element("Replace")
19    item = SubElement(replace, "Item")
20
21    # Meta section
22    meta = SubElement(item, "Meta")
23    format_elem = SubElement(meta, "Format")
24    format_elem.set("xmlns", "syncml:metinf")
25    format_elem.text = "chr"
26
27    # Target section
28    target = SubElement(item, "Target")
29    loc_uri_elem = SubElement(target, "LocURI")
30    loc_uri_elem.text = loc_uri
31
32    # Data section
33    data = SubElement(item, "Data")
34    data.text = data_value
35
36    return replace

Create a Replace/Item element with the specified LocURI and Data

class MDMConfigResponseSerializer(authentik.core.api.utils.PassiveSerializer):
39class MDMConfigResponseSerializer(PassiveSerializer):
40
41    config = CharField(required=True)
42    mime_type = CharField(required=True)
43    filename = CharField(required=True)

Base serializer class which doesn't implement create/update methods

config
mime_type
filename
 46class AgentConnectorController(BaseController[AgentConnector]):
 47
 48    @staticmethod
 49    def vendor_identifier() -> str:
 50        return "goauthentik.io/platform"
 51
 52    def capabilities(self) -> list[Capabilities]:
 53        return [Capabilities.STAGE_ENDPOINTS]
 54
 55    def generate_mdm_config(
 56        self, target_platform: OSFamily, request: HttpRequest, token: EnrollmentToken
 57    ) -> MDMConfigResponseSerializer:
 58        response = None
 59        if target_platform == OSFamily.windows:
 60            response = self._generate_mdm_config_windows(request, token)
 61        if target_platform in [OSFamily.iOS, OSFamily.macOS]:
 62            response = self._generate_mdm_config_macos(request, token)
 63        if not response:
 64            raise ValueError(f"Unsupported platform for MDM Configuration: {target_platform}")
 65        response.is_valid(raise_exception=True)
 66        return response
 67
 68    def _generate_mdm_config_windows(
 69        self, request: HttpRequest, token: EnrollmentToken
 70    ) -> MDMConfigResponseSerializer:
 71        base_uri = (
 72            "./Vendor/MSFT/Registry/HKLM/SOFTWARE/authentik Security Inc./Platform/ManagedConfig"
 73        )
 74        token_item = csp_create_replace_item(
 75            base_uri + "/RegistrationToken",
 76            token.key,
 77        )
 78        url_item = csp_create_replace_item(
 79            base_uri + "/URL",
 80            request.build_absolute_uri(reverse("authentik_core:root-redirect")),
 81        )
 82
 83        payload = tostring(token_item, encoding="unicode") + tostring(url_item, encoding="unicode")
 84        return MDMConfigResponseSerializer(
 85            data={
 86                "config": payload,
 87                "mime_type": "application/xml",
 88                "filename": f"{self.connector.name}_config.csp.xml",
 89            }
 90        )
 91
 92    def _generate_mdm_config_macos(
 93        self, request: HttpRequest, token: EnrollmentToken
 94    ) -> MDMConfigResponseSerializer:
 95        token_uuid = str(token.pk).upper()
 96        payload = dumps(
 97            {
 98                "PayloadContent": [
 99                    # Config for authentik Platform Agent (sysd)
100                    {
101                        "PayloadDisplayName": "authentik Platform",
102                        "PayloadIdentifier": f"io.goauthentik.platform.{token_uuid}",
103                        "PayloadType": "io.goauthentik.platform",
104                        "PayloadUUID": str(uuid4()),
105                        "PayloadVersion": 1,
106                        "RegistrationToken": token.key,
107                        "URL": request.build_absolute_uri(reverse("authentik_core:root-redirect")),
108                    },
109                    # Config for MDM-associated domains (required for PSSO)
110                    {
111                        "PayloadDisplayName": "Associated Domains",
112                        "PayloadIdentifier": f"com.apple.associated-domains.{token_uuid}",
113                        "PayloadType": "com.apple.associated-domains",
114                        "PayloadUUID": str(uuid4()),
115                        "PayloadVersion": 1,
116                        "Configuration": [
117                            {
118                                "ApplicationIdentifier": "232G855Y8N.io.goauthentik.platform.agent",
119                                "AssociatedDomains": [f"authsrv:{request.get_host()}"],
120                                "EnableDirectDownloads": False,
121                            }
122                        ],
123                    },
124                    # Config for Platform SSO
125                    {
126                        "PayloadDisplayName": "Platform Single Sign-On",
127                        "PayloadIdentifier": f"com.apple.extensiblesso.{token_uuid}",
128                        "PayloadType": "com.apple.extensiblesso",
129                        "PayloadUUID": str(uuid4()),
130                        "PayloadVersion": 1,
131                        "ExtensionIdentifier": "io.goauthentik.platform.psso",
132                        "TeamIdentifier": "232G855Y8N",
133                        "Type": "Redirect",
134                        "URLs": [
135                            request.build_absolute_uri(reverse("authentik_core:root-redirect")),
136                        ],
137                        "PlatformSSO": {
138                            "AccountDisplayName": "authentik",
139                            "AllowDeviceIdentifiersInAttestation": True,
140                            "AuthenticationMethod": "UserSecureEnclaveKey",
141                            "EnableAuthorization": True,
142                            "EnableCreateUserAtLogin": True,
143                            "FileVaultPolicy": ["RequireAuthentication"],
144                            "LoginPolicy": ["RequireAuthentication"],
145                            "NewUserAuthorizationMode": "Standard",
146                            "UnlockPolicy": ["RequireAuthentication"],
147                            "UseSharedDeviceKeys": True,
148                            "UserAuthorizationMode": "Standard",
149                        },
150                    },
151                ],
152                "PayloadDisplayName": "authentik Platform",
153                "PayloadIdentifier": str(self.connector.pk).upper(),
154                "PayloadScope": "System",
155                "PayloadType": "Configuration",
156                "PayloadUUID": str(self.connector.pk).upper(),
157                "PayloadVersion": 1,
158            },
159            fmt=PlistFormat.FMT_XML,
160        ).decode()
161        return MDMConfigResponseSerializer(
162            data={
163                "config": payload,
164                "mime_type": "application/xml",
165                "filename": f"{self.connector.name}_config.mobileconfig",
166            }
167        )

Abstract base class for generic types.

On Python 3.12 and newer, generic classes implicitly inherit from Generic when they declare a parameter list after the class's name::

class Mapping[KT, VT]:
    def __getitem__(self, key: KT) -> VT:
        ...
    # Etc.

On older versions of Python, however, generic classes have to explicitly inherit from Generic.

After a class has been declared to be generic, it can then be used as follows::

def lookup_name[KT, VT](mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
    try:
        return mapping[key]
    except KeyError:
        return default
@staticmethod
def vendor_identifier() -> str:
48    @staticmethod
49    def vendor_identifier() -> str:
50        return "goauthentik.io/platform"
def capabilities(self) -> list[authentik.endpoints.controller.Capabilities]:
52    def capabilities(self) -> list[Capabilities]:
53        return [Capabilities.STAGE_ENDPOINTS]
def generate_mdm_config( self, target_platform: authentik.endpoints.facts.OSFamily, request: django.http.request.HttpRequest, token: authentik.endpoints.connectors.agent.models.EnrollmentToken) -> MDMConfigResponseSerializer:
55    def generate_mdm_config(
56        self, target_platform: OSFamily, request: HttpRequest, token: EnrollmentToken
57    ) -> MDMConfigResponseSerializer:
58        response = None
59        if target_platform == OSFamily.windows:
60            response = self._generate_mdm_config_windows(request, token)
61        if target_platform in [OSFamily.iOS, OSFamily.macOS]:
62            response = self._generate_mdm_config_macos(request, token)
63        if not response:
64            raise ValueError(f"Unsupported platform for MDM Configuration: {target_platform}")
65        response.is_valid(raise_exception=True)
66        return response