Compare commits
	
		
			8 Commits
		
	
	
		
			docusaurus
			...
			enterprise
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 42a99e3672 | |||
| 4e501f2fbf | |||
| 1cca629464 | |||
| 4efdc3113e | |||
| 5a9b0f7b7a | |||
| 395ccc5af1 | |||
| c8ac4fcdd6 | |||
| 53c36394e9 | 
| @ -43,6 +43,7 @@ from authentik.core.models import ( | ||||
| ) | ||||
| from authentik.enterprise.license import LicenseKey | ||||
| from authentik.enterprise.models import LicenseUsage | ||||
| from authentik.enterprise.providers.apple_psso.models import AppleNonce | ||||
| from authentik.enterprise.providers.google_workspace.models import ( | ||||
|     GoogleWorkspaceProviderGroup, | ||||
|     GoogleWorkspaceProviderUser, | ||||
| @ -135,6 +136,7 @@ def excluded_models() -> list[type[Model]]: | ||||
|         EndpointDeviceConnection, | ||||
|         DeviceToken, | ||||
|         StreamEvent, | ||||
|         AppleNonce, | ||||
|     ) | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										32
									
								
								authentik/enterprise/providers/apple_psso/api/providers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								authentik/enterprise/providers/apple_psso/api/providers.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| """Apple Platform SSO Provider API Views""" | ||||
|  | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.core.api.providers import ProviderSerializer | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.enterprise.api import EnterpriseRequiredMixin | ||||
| from authentik.enterprise.providers.apple_psso.models import ApplePlatformSSOProvider | ||||
|  | ||||
|  | ||||
| class ApplePlatformSSOProviderSerializer(EnterpriseRequiredMixin, ProviderSerializer): | ||||
|     """ApplePlatformSSOProvider Serializer""" | ||||
|  | ||||
|     class Meta: | ||||
|         model = ApplePlatformSSOProvider | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "name", | ||||
|         ] | ||||
|         extra_kwargs = {} | ||||
|  | ||||
|  | ||||
| class ApplePlatformSSOProviderViewSet(UsedByMixin, ModelViewSet): | ||||
|     """ApplePlatformSSOProvider Viewset""" | ||||
|  | ||||
|     queryset = ApplePlatformSSOProvider.objects.all() | ||||
|     serializer_class = ApplePlatformSSOProviderSerializer | ||||
|     filterset_fields = [ | ||||
|         "name", | ||||
|     ] | ||||
|     search_fields = ["name"] | ||||
|     ordering = ["name"] | ||||
							
								
								
									
										13
									
								
								authentik/enterprise/providers/apple_psso/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								authentik/enterprise/providers/apple_psso/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| from authentik.enterprise.apps import EnterpriseConfig | ||||
|  | ||||
|  | ||||
| class AuthentikEnterpriseProviderApplePSSOConfig(EnterpriseConfig): | ||||
|  | ||||
|     name = "authentik.enterprise.providers.apple_psso" | ||||
|     label = "authentik_providers_apple_psso" | ||||
|     verbose_name = "authentik Enterprise.Providers.Apple Platform SSO" | ||||
|     default = True | ||||
|     mountpoints = { | ||||
|         "authentik.enterprise.providers.apple_psso.urls": "endpoint/apple/sso/", | ||||
|         "authentik.enterprise.providers.apple_psso.urls_root": "", | ||||
|     } | ||||
							
								
								
									
										118
									
								
								authentik/enterprise/providers/apple_psso/http.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								authentik/enterprise/providers/apple_psso/http.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | ||||
| from base64 import urlsafe_b64encode | ||||
| from json import dumps | ||||
| from secrets import token_bytes | ||||
|  | ||||
| from cryptography.hazmat.backends import default_backend | ||||
| from cryptography.hazmat.primitives import hashes, serialization | ||||
| from cryptography.hazmat.primitives.asymmetric import ec | ||||
| from cryptography.hazmat.primitives.ciphers.aead import AESGCM | ||||
| from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash | ||||
| from django.http import HttpResponse | ||||
| from jwcrypto.common import base64url_decode, base64url_encode | ||||
|  | ||||
| from authentik.enterprise.providers.apple_psso.models import AppleDevice | ||||
|  | ||||
|  | ||||
| def length_prefixed(data: bytes) -> bytes: | ||||
|     length = len(data) | ||||
|     return length.to_bytes(4, "big") + data | ||||
|  | ||||
|  | ||||
| def build_apu(public_key: ec.EllipticCurvePublicKey): | ||||
|     # X9.63 representation: 0x04 || X || Y | ||||
|     public_numbers = public_key.public_numbers() | ||||
|  | ||||
|     x_bytes = public_numbers.x.to_bytes(32, "big") | ||||
|     y_bytes = public_numbers.y.to_bytes(32, "big") | ||||
|  | ||||
|     x963 = bytes([0x04]) + x_bytes + y_bytes | ||||
|  | ||||
|     result = length_prefixed(b"APPLE") + length_prefixed(x963) | ||||
|  | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def encrypt_token_with_a256_gcm(body: dict, device_encryption_key: str, apv: bytes) -> str: | ||||
|     ephemeral_key = ec.generate_private_key(curve=ec.SECP256R1()) | ||||
|     device_public_key = serialization.load_pem_public_key( | ||||
|         device_encryption_key.encode(), backend=default_backend() | ||||
|     ) | ||||
|  | ||||
|     shared_secret_z = ephemeral_key.exchange(ec.ECDH(), device_public_key) | ||||
|  | ||||
|     apu = build_apu(ephemeral_key.public_key()) | ||||
|  | ||||
|     jwe_header = { | ||||
|         "enc": "A256GCM", | ||||
|         "kid": "ephemeralKey", | ||||
|         "epk": { | ||||
|             "x": base64url_encode( | ||||
|                 ephemeral_key.public_key().public_numbers().x.to_bytes(32, "big") | ||||
|             ), | ||||
|             "y": base64url_encode( | ||||
|                 ephemeral_key.public_key().public_numbers().y.to_bytes(32, "big") | ||||
|             ), | ||||
|             "kty": "EC", | ||||
|             "crv": "P-256", | ||||
|         }, | ||||
|         "typ": "platformsso-login-response+jwt", | ||||
|         "alg": "ECDH-ES", | ||||
|         "apu": base64url_encode(apu), | ||||
|         "apv": base64url_encode(apv), | ||||
|     } | ||||
|  | ||||
|     party_u_info = length_prefixed(apu) | ||||
|     party_v_info = length_prefixed(apv) | ||||
|     supp_pub_info = (256).to_bytes(4, "big") | ||||
|  | ||||
|     other_info = length_prefixed(b"A256GCM") + party_u_info + party_v_info + supp_pub_info | ||||
|  | ||||
|     ckdf = ConcatKDFHash( | ||||
|         algorithm=hashes.SHA256(), | ||||
|         length=32, | ||||
|         otherinfo=other_info, | ||||
|     ) | ||||
|  | ||||
|     derived_key = ckdf.derive(shared_secret_z) | ||||
|  | ||||
|     nonce = token_bytes(12) | ||||
|  | ||||
|     header_json = dumps(jwe_header, separators=(",", ":")).encode() | ||||
|     aad = urlsafe_b64encode(header_json).rstrip(b"=") | ||||
|  | ||||
|     aesgcm = AESGCM(derived_key) | ||||
|     ciphertext = aesgcm.encrypt(nonce, dumps(body).encode(), aad) | ||||
|  | ||||
|     ciphertext_body = ciphertext[:-16] | ||||
|     tag = ciphertext[-16:] | ||||
|  | ||||
|     # base64url encoding | ||||
|     protected_b64 = urlsafe_b64encode(header_json).rstrip(b"=") | ||||
|     iv_b64 = urlsafe_b64encode(nonce).rstrip(b"=") | ||||
|     ciphertext_b64 = urlsafe_b64encode(ciphertext_body).rstrip(b"=") | ||||
|     tag_b64 = urlsafe_b64encode(tag).rstrip(b"=") | ||||
|  | ||||
|     jwe_compact = b".".join( | ||||
|         [ | ||||
|             protected_b64, | ||||
|             b"", | ||||
|             iv_b64, | ||||
|             ciphertext_b64, | ||||
|             tag_b64, | ||||
|         ] | ||||
|     ) | ||||
|     return jwe_compact.decode() | ||||
|  | ||||
|  | ||||
| class JWEResponse(HttpResponse): | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         data: dict, | ||||
|         device: AppleDevice, | ||||
|         apv: str, | ||||
|     ): | ||||
|         super().__init__( | ||||
|             content=encrypt_token_with_a256_gcm(data, device.encryption_key, base64url_decode(apv)), | ||||
|             content_type="application/platformsso-login-response+jwt", | ||||
|         ) | ||||
| @ -0,0 +1,36 @@ | ||||
| # Generated by Django 5.1.11 on 2025-06-28 00:12 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_providers_oauth2", "0028_migrate_session"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name="ApplePlatformSSOProvider", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "oauth2provider_ptr", | ||||
|                     models.OneToOneField( | ||||
|                         auto_created=True, | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         parent_link=True, | ||||
|                         primary_key=True, | ||||
|                         serialize=False, | ||||
|                         to="authentik_providers_oauth2.oauth2provider", | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|             options={ | ||||
|                 "abstract": False, | ||||
|             }, | ||||
|             bases=("authentik_providers_oauth2.oauth2provider",), | ||||
|         ), | ||||
|     ] | ||||
| @ -0,0 +1,94 @@ | ||||
| # Generated by Django 5.1.11 on 2025-06-28 15:50 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| import uuid | ||||
| from django.conf import settings | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_providers_apple_psso", "0001_initial"), | ||||
|         migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name="AppleDevice", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "endpoint_uuid", | ||||
|                     models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False), | ||||
|                 ), | ||||
|                 ("signing_key", models.TextField()), | ||||
|                 ("encryption_key", models.TextField()), | ||||
|                 ("key_exchange_key", models.TextField()), | ||||
|                 ("sign_key_id", models.TextField()), | ||||
|                 ("enc_key_id", models.TextField()), | ||||
|                 ("creation_time", models.DateTimeField(auto_now_add=True)), | ||||
|                 ( | ||||
|                     "provider", | ||||
|                     models.ForeignKey( | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         to="authentik_providers_apple_psso.appleplatformssoprovider", | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name="AppleDeviceUser", | ||||
|             fields=[ | ||||
|                 ("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), | ||||
|                 ("signing_key", models.TextField()), | ||||
|                 ("encryption_key", models.TextField()), | ||||
|                 ("sign_key_id", models.TextField()), | ||||
|                 ("enc_key_id", models.TextField()), | ||||
|                 ( | ||||
|                     "device", | ||||
|                     models.ForeignKey( | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         to="authentik_providers_apple_psso.appledevice", | ||||
|                     ), | ||||
|                 ), | ||||
|                 ( | ||||
|                     "user", | ||||
|                     models.ForeignKey( | ||||
|                         on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="appledevice", | ||||
|             name="users", | ||||
|             field=models.ManyToManyField( | ||||
|                 through="authentik_providers_apple_psso.AppleDeviceUser", | ||||
|                 to=settings.AUTH_USER_MODEL, | ||||
|             ), | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name="AppleNonce", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "id", | ||||
|                     models.AutoField( | ||||
|                         auto_created=True, primary_key=True, serialize=False, verbose_name="ID" | ||||
|                     ), | ||||
|                 ), | ||||
|                 ("expires", models.DateTimeField(default=None, null=True)), | ||||
|                 ("expiring", models.BooleanField(default=True)), | ||||
|                 ("nonce", models.TextField()), | ||||
|             ], | ||||
|             options={ | ||||
|                 "abstract": False, | ||||
|                 "indexes": [ | ||||
|                     models.Index(fields=["expires"], name="authentik_p_expires_47d534_idx"), | ||||
|                     models.Index(fields=["expiring"], name="authentik_p_expirin_87253e_idx"), | ||||
|                     models.Index( | ||||
|                         fields=["expiring", "expires"], name="authentik_p_expirin_20a7c9_idx" | ||||
|                     ), | ||||
|                 ], | ||||
|             }, | ||||
|         ), | ||||
|     ] | ||||
| @ -0,0 +1,34 @@ | ||||
| # Generated by Django 5.1.11 on 2025-06-28 22:18 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ( | ||||
|             "authentik_providers_apple_psso", | ||||
|             "0002_appledevice_appledeviceuser_appledevice_users_and_more", | ||||
|         ), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.RenameField( | ||||
|             model_name="appledeviceuser", | ||||
|             old_name="sign_key_id", | ||||
|             new_name="enclave_key_id", | ||||
|         ), | ||||
|         migrations.RenameField( | ||||
|             model_name="appledeviceuser", | ||||
|             old_name="signing_key", | ||||
|             new_name="secure_enclave_key", | ||||
|         ), | ||||
|         migrations.RemoveField( | ||||
|             model_name="appledeviceuser", | ||||
|             name="enc_key_id", | ||||
|         ), | ||||
|         migrations.RemoveField( | ||||
|             model_name="appledeviceuser", | ||||
|             name="encryption_key", | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										85
									
								
								authentik/enterprise/providers/apple_psso/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								authentik/enterprise/providers/apple_psso/models.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| from uuid import uuid4 | ||||
|  | ||||
| from django.db import models | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from rest_framework.serializers import Serializer | ||||
|  | ||||
| from authentik.core.models import ExpiringModel, User | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.providers.oauth2.models import ( | ||||
|     ClientTypes, | ||||
|     IssuerMode, | ||||
|     OAuth2Provider, | ||||
|     RedirectURI, | ||||
|     RedirectURIMatchingMode, | ||||
|     ScopeMapping, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class ApplePlatformSSOProvider(OAuth2Provider): | ||||
|     """Integrate with Apple Platform SSO""" | ||||
|  | ||||
|     def set_oauth_defaults(self): | ||||
|         """Ensure all OAuth2-related settings are correct""" | ||||
|         self.issuer_mode = IssuerMode.PER_PROVIDER | ||||
|         self.client_type = ClientTypes.PUBLIC | ||||
|         self.signing_key = CertificateKeyPair.objects.get(name="authentik Self-signed Certificate") | ||||
|         self.include_claims_in_id_token = True | ||||
|         scopes = ScopeMapping.objects.filter( | ||||
|             managed__in=[ | ||||
|                 "goauthentik.io/providers/oauth2/scope-openid", | ||||
|                 "goauthentik.io/providers/oauth2/scope-profile", | ||||
|                 "goauthentik.io/providers/oauth2/scope-email", | ||||
|                 "goauthentik.io/providers/oauth2/scope-offline_access", | ||||
|                 "goauthentik.io/providers/oauth2/scope-authentik_api", | ||||
|             ] | ||||
|         ) | ||||
|         self.property_mappings.add(*list(scopes)) | ||||
|         self.redirect_uris = [ | ||||
|             RedirectURI(RedirectURIMatchingMode.STRICT, "io.goauthentik.endpoint:/oauth2redirect"), | ||||
|         ] | ||||
|  | ||||
|     @property | ||||
|     def component(self) -> str: | ||||
|         return "ak-provider-apple-psso-form" | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> type[Serializer]: | ||||
|         from authentik.enterprise.providers.apple_psso.api.providers import ( | ||||
|             ApplePlatformSSOProviderSerializer, | ||||
|         ) | ||||
|  | ||||
|         return ApplePlatformSSOProviderSerializer | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("Apple Platform SSO Provider") | ||||
|         verbose_name_plural = _("Apple Platform SSO Providers") | ||||
|  | ||||
|  | ||||
| class AppleDevice(models.Model): | ||||
|  | ||||
|     endpoint_uuid = models.UUIDField(default=uuid4, primary_key=True) | ||||
|  | ||||
|     signing_key = models.TextField() | ||||
|     encryption_key = models.TextField() | ||||
|     key_exchange_key = models.TextField() | ||||
|     sign_key_id = models.TextField() | ||||
|     enc_key_id = models.TextField() | ||||
|     creation_time = models.DateTimeField(auto_now_add=True) | ||||
|     provider = models.ForeignKey(ApplePlatformSSOProvider, on_delete=models.CASCADE) | ||||
|     users = models.ManyToManyField(User, through="AppleDeviceUser") | ||||
|  | ||||
|  | ||||
| class AppleDeviceUser(models.Model): | ||||
|  | ||||
|     uuid = models.UUIDField(default=uuid4, primary_key=True) | ||||
|  | ||||
|     device = models.ForeignKey(AppleDevice, on_delete=models.CASCADE) | ||||
|     user = models.ForeignKey(User, on_delete=models.CASCADE) | ||||
|  | ||||
|     secure_enclave_key = models.TextField() | ||||
|     enclave_key_id = models.TextField() | ||||
|  | ||||
|  | ||||
| class AppleNonce(ExpiringModel): | ||||
|     nonce = models.TextField() | ||||
							
								
								
									
										15
									
								
								authentik/enterprise/providers/apple_psso/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								authentik/enterprise/providers/apple_psso/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| from django.urls import path | ||||
|  | ||||
| from authentik.enterprise.providers.apple_psso.views.nonce import NonceView | ||||
| from authentik.enterprise.providers.apple_psso.views.register import ( | ||||
|     RegisterDeviceView, | ||||
|     RegisterUserView, | ||||
| ) | ||||
| from authentik.enterprise.providers.apple_psso.views.token import TokenView | ||||
|  | ||||
| urlpatterns = [ | ||||
|     path("token/", TokenView.as_view(), name="token"), | ||||
|     path("nonce/", NonceView.as_view(), name="nonce"), | ||||
|     path("register/device/", RegisterDeviceView.as_view(), name="register-device"), | ||||
|     path("register/user/", RegisterUserView.as_view(), name="register-user"), | ||||
| ] | ||||
							
								
								
									
										7
									
								
								authentik/enterprise/providers/apple_psso/urls_root.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								authentik/enterprise/providers/apple_psso/urls_root.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| from django.urls import path | ||||
|  | ||||
| from authentik.enterprise.providers.apple_psso.views.site_association import AppleAppSiteAssociation | ||||
|  | ||||
| urlpatterns = [ | ||||
|     path(".well-known/apple-app-site-association", AppleAppSiteAssociation.as_view(), name="asa"), | ||||
| ] | ||||
							
								
								
									
										25
									
								
								authentik/enterprise/providers/apple_psso/views/nonce.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								authentik/enterprise/providers/apple_psso/views/nonce.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| from base64 import b64encode | ||||
| from datetime import timedelta | ||||
| from secrets import token_bytes | ||||
|  | ||||
| from django.http import HttpRequest, JsonResponse | ||||
| from django.utils.decorators import method_decorator | ||||
| from django.utils.timezone import now | ||||
| from django.views import View | ||||
| from django.views.decorators.csrf import csrf_exempt | ||||
|  | ||||
| from authentik.enterprise.providers.apple_psso.models import AppleNonce | ||||
|  | ||||
|  | ||||
| @method_decorator(csrf_exempt, name="dispatch") | ||||
| class NonceView(View): | ||||
|  | ||||
|     def post(self, request: HttpRequest, *args, **kwargs): | ||||
|         nonce = AppleNonce.objects.create( | ||||
|             nonce=b64encode(token_bytes(32)).decode(), expires=now() + timedelta(minutes=5) | ||||
|         ) | ||||
|         return JsonResponse( | ||||
|             { | ||||
|                 "Nonce": nonce.nonce, | ||||
|             } | ||||
|         ) | ||||
							
								
								
									
										92
									
								
								authentik/enterprise/providers/apple_psso/views/register.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								authentik/enterprise/providers/apple_psso/views/register.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | ||||
| from django.shortcuts import get_object_or_404 | ||||
| from rest_framework.authentication import BaseAuthentication | ||||
| from rest_framework.fields import CharField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.views import APIView | ||||
|  | ||||
| from authentik.api.authentication import TokenAuthentication | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.core.models import User | ||||
| from authentik.enterprise.providers.apple_psso.models import ( | ||||
|     AppleDevice, | ||||
|     AppleDeviceUser, | ||||
|     ApplePlatformSSOProvider, | ||||
| ) | ||||
| from authentik.lib.generators import generate_key | ||||
|  | ||||
|  | ||||
| class DeviceRegisterAuth(BaseAuthentication): | ||||
|     def authenticate(self, request): | ||||
|         # very temporary, lol | ||||
|         return (User(), None) | ||||
|  | ||||
|  | ||||
| class RegisterDeviceView(APIView): | ||||
|  | ||||
|     class DeviceRegistration(PassiveSerializer): | ||||
|  | ||||
|         device_uuid = CharField() | ||||
|         client_id = CharField() | ||||
|         device_signing_key = CharField() | ||||
|         device_encryption_key = CharField() | ||||
|         sign_key_id = CharField() | ||||
|         enc_key_id = CharField() | ||||
|  | ||||
|     permission_classes = [] | ||||
|     pagination_class = None | ||||
|     filter_backends = [] | ||||
|     serializer_class = DeviceRegistration | ||||
|     authentication_classes = [DeviceRegisterAuth, TokenAuthentication] | ||||
|  | ||||
|     def post(self, request: Request) -> Response: | ||||
|         data = self.DeviceRegistration(data=request.data) | ||||
|         data.is_valid(raise_exception=True) | ||||
|         provider = get_object_or_404( | ||||
|             ApplePlatformSSOProvider, client_id=data.validated_data["client_id"] | ||||
|         ) | ||||
|         AppleDevice.objects.update_or_create( | ||||
|             endpoint_uuid=data.validated_data["device_uuid"], | ||||
|             defaults={ | ||||
|                 "signing_key": data.validated_data["device_signing_key"], | ||||
|                 "encryption_key": data.validated_data["device_encryption_key"], | ||||
|                 "sign_key_id": data.validated_data["sign_key_id"], | ||||
|                 "enc_key_id": data.validated_data["enc_key_id"], | ||||
|                 "key_exchange_key": generate_key(), | ||||
|                 "provider": provider, | ||||
|             }, | ||||
|         ) | ||||
|         return Response() | ||||
|  | ||||
|  | ||||
| class RegisterUserView(APIView): | ||||
|  | ||||
|     class UserRegistration(PassiveSerializer): | ||||
|  | ||||
|         device_uuid = CharField() | ||||
|         user_secure_enclave_key = CharField() | ||||
|         enclave_key_id = CharField() | ||||
|  | ||||
|     permission_classes = [] | ||||
|     pagination_class = None | ||||
|     filter_backends = [] | ||||
|     serializer_class = UserRegistration | ||||
|     authentication_classes = [TokenAuthentication] | ||||
|  | ||||
|     def post(self, request: Request) -> Response: | ||||
|         data = self.UserRegistration(data=request.data) | ||||
|         data.is_valid(raise_exception=True) | ||||
|         device = get_object_or_404(AppleDevice, endpoint_uuid=data.validated_data["device_uuid"]) | ||||
|         AppleDeviceUser.objects.update_or_create( | ||||
|             device=device, | ||||
|             user=request.user, | ||||
|             defaults={ | ||||
|                 "secure_enclave_key": data.validated_data["user_secure_enclave_key"], | ||||
|                 "enclave_key_id": data.validated_data["enclave_key_id"], | ||||
|             }, | ||||
|         ) | ||||
|         return Response( | ||||
|             { | ||||
|                 "username": request.user.username, | ||||
|             } | ||||
|         ) | ||||
| @ -0,0 +1,16 @@ | ||||
| from django.http import HttpRequest, HttpResponse, JsonResponse | ||||
| from django.views import View | ||||
|  | ||||
|  | ||||
| class AppleAppSiteAssociation(View): | ||||
|     def get(self, request: HttpRequest) -> HttpResponse: | ||||
|         return JsonResponse( | ||||
|             { | ||||
|                 "authsrv": { | ||||
|                     "apps": [ | ||||
|                         "232G855Y8N.io.goauthentik.endpoint", | ||||
|                         "232G855Y8N.io.goauthentik.endpoint.psso", | ||||
|                     ] | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
							
								
								
									
										140
									
								
								authentik/enterprise/providers/apple_psso/views/token.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								authentik/enterprise/providers/apple_psso/views/token.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | ||||
| from datetime import timedelta | ||||
|  | ||||
| from django.http import Http404, HttpRequest, HttpResponse | ||||
| from django.utils.decorators import method_decorator | ||||
| from django.utils.timezone import now | ||||
| from django.views import View | ||||
| from django.views.decorators.csrf import csrf_exempt | ||||
| from jwt import PyJWT, decode | ||||
| from rest_framework.exceptions import ValidationError | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.models import AuthenticatedSession, Session, User | ||||
| from authentik.core.sessions import SessionStore | ||||
| from authentik.enterprise.providers.apple_psso.http import JWEResponse | ||||
| from authentik.enterprise.providers.apple_psso.models import ( | ||||
|     AppleDevice, | ||||
|     AppleDeviceUser, | ||||
|     AppleNonce, | ||||
|     ApplePlatformSSOProvider, | ||||
| ) | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.events.signals import SESSION_LOGIN_EVENT | ||||
| from authentik.providers.oauth2.constants import TOKEN_TYPE | ||||
| from authentik.providers.oauth2.id_token import IDToken | ||||
| from authentik.providers.oauth2.models import RefreshToken | ||||
| from authentik.root.middleware import SessionMiddleware | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| @method_decorator(csrf_exempt, name="dispatch") | ||||
| class TokenView(View): | ||||
|  | ||||
|     device: AppleDevice | ||||
|     provider: ApplePlatformSSOProvider | ||||
|  | ||||
|     def post(self, request: HttpRequest) -> HttpResponse: | ||||
|         version = request.POST.get("platform_sso_version") | ||||
|         assertion = request.POST.get("assertion", request.POST.get("request")) | ||||
|         if not assertion: | ||||
|             return HttpResponse(status=400) | ||||
|  | ||||
|         decode_unvalidated = PyJWT().decode_complete(assertion, options={"verify_signature": False}) | ||||
|         LOGGER.debug(decode_unvalidated["header"]) | ||||
|         expected_kid = decode_unvalidated["header"]["kid"] | ||||
|  | ||||
|         self.device = AppleDevice.objects.filter(sign_key_id=expected_kid).first() | ||||
|         if not self.device: | ||||
|             raise Http404 | ||||
|         self.provider = self.device.provider | ||||
|  | ||||
|         # Properly decode the JWT with the key from the device | ||||
|         decoded = decode( | ||||
|             assertion, self.device.signing_key, algorithms=["ES256"], options={"verify_aud": False} | ||||
|         ) | ||||
|         LOGGER.debug(decoded) | ||||
|  | ||||
|         LOGGER.debug("got device", device=self.device) | ||||
|  | ||||
|         # Check that the nonce hasn't been used before | ||||
|         nonce = AppleNonce.objects.filter(nonce=decoded["request_nonce"]).first() | ||||
|         if not nonce: | ||||
|             return HttpResponse(status=400) | ||||
|         nonce.delete() | ||||
|  | ||||
|         handler_func = ( | ||||
|             f"handle_v{version}_{decode_unvalidated["header"]["typ"]}".replace("-", "_") | ||||
|             .replace("+", "_") | ||||
|             .replace(".", "_") | ||||
|         ) | ||||
|         handler = getattr(self, handler_func, None) | ||||
|         if not handler: | ||||
|             LOGGER.debug("Handler not found", handler=handler_func) | ||||
|             return HttpResponse(status=400) | ||||
|         LOGGER.debug("sending to handler", handler=handler_func) | ||||
|         return handler(decoded) | ||||
|  | ||||
|     def validate_device_user_response(self, assertion: str) -> tuple[AppleDeviceUser, dict] | None: | ||||
|         """Decode an embedded assertion and validate it by looking up the matching device user""" | ||||
|         decode_unvalidated = PyJWT().decode_complete(assertion, options={"verify_signature": False}) | ||||
|         expected_kid = decode_unvalidated["header"]["kid"] | ||||
|  | ||||
|         device_user = AppleDeviceUser.objects.filter( | ||||
|             device=self.device, enclave_key_id=expected_kid | ||||
|         ).first() | ||||
|         if not device_user: | ||||
|             return None | ||||
|         return device_user, decode( | ||||
|             assertion, | ||||
|             device_user.secure_enclave_key, | ||||
|             audience="apple-platform-sso", | ||||
|             algorithms=["ES256"], | ||||
|         ) | ||||
|  | ||||
|     def create_auth_session(self, user: User): | ||||
|         event = Event.new(EventAction.LOGIN).from_http(self.request, user=user) | ||||
|         store = SessionStore() | ||||
|         store[SESSION_LOGIN_EVENT] = event | ||||
|         store.save() | ||||
|         session = Session.objects.filter(session_key=store.session_key).first() | ||||
|         AuthenticatedSession.objects.create(session=session, user=user) | ||||
|         session = SessionMiddleware.encode_session(store.session_key, user) | ||||
|         return session | ||||
|  | ||||
|     def handle_v1_0_platformsso_login_request_jwt(self, decoded: dict): | ||||
|         user = None | ||||
|         if decoded["grant_type"] == "urn:ietf:params:oauth:grant-type:jwt-bearer": | ||||
|             # Decode and validate inner assertion | ||||
|             user, inner = self.validate_device_user_response(decoded["assertion"]) | ||||
|             if inner["nonce"] != decoded["nonce"]: | ||||
|                 LOGGER.warning("Mis-matched nonce to outer assertion") | ||||
|                 raise ValidationError("Invalid request") | ||||
|  | ||||
|         refresh_token = RefreshToken( | ||||
|             user=user.user, | ||||
|             scope=decoded["scope"], | ||||
|             expires=now() + timedelta(hours=8), | ||||
|             provider=self.provider, | ||||
|             auth_time=now(), | ||||
|             session=None, | ||||
|         ) | ||||
|         id_token = IDToken.new( | ||||
|             self.provider, | ||||
|             refresh_token, | ||||
|             self.request, | ||||
|         ) | ||||
|         id_token.nonce = decoded["nonce"] | ||||
|         refresh_token.id_token = id_token | ||||
|         refresh_token.save() | ||||
|         return JWEResponse( | ||||
|             { | ||||
|                 "refresh_token": refresh_token.token, | ||||
|                 "refresh_token_expires_in": int((refresh_token.expires - now()).total_seconds()), | ||||
|                 "id_token": refresh_token.id_token.to_jwt(self.provider), | ||||
|                 "token_type": TOKEN_TYPE, | ||||
|                 "session_key": self.create_auth_session(user.user), | ||||
|             }, | ||||
|             device=self.device, | ||||
|             apv=decoded["jwe_crypto"]["apv"], | ||||
|         ) | ||||
| @ -15,6 +15,7 @@ CELERY_BEAT_SCHEDULE = { | ||||
| TENANT_APPS = [ | ||||
|     "authentik.enterprise.audit", | ||||
|     "authentik.enterprise.policies.unique_password", | ||||
|     "authentik.enterprise.providers.apple_psso", | ||||
|     "authentik.enterprise.providers.google_workspace", | ||||
|     "authentik.enterprise.providers.microsoft_entra", | ||||
|     "authentik.enterprise.providers.ssf", | ||||
|  | ||||
| @ -555,6 +555,8 @@ class TokenView(View): | ||||
|  | ||||
|     provider: OAuth2Provider | None = None | ||||
|     params: TokenParams | None = None | ||||
|     params_class = TokenParams | ||||
|     provider_class = OAuth2Provider | ||||
|  | ||||
|     def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: | ||||
|         response = super().dispatch(request, *args, **kwargs) | ||||
| @ -574,12 +576,14 @@ class TokenView(View): | ||||
|                 op="authentik.providers.oauth2.post.parse", | ||||
|             ): | ||||
|                 client_id, client_secret = extract_client_auth(request) | ||||
|                 self.provider = OAuth2Provider.objects.filter(client_id=client_id).first() | ||||
|                 self.provider = self.provider_class.objects.filter(client_id=client_id).first() | ||||
|                 if not self.provider: | ||||
|                     LOGGER.warning("OAuth2Provider does not exist", client_id=client_id) | ||||
|                     raise TokenError("invalid_client") | ||||
|                 CTX_AUTH_VIA.set("oauth_client_secret") | ||||
|                 self.params = TokenParams.parse(request, self.provider, client_id, client_secret) | ||||
|                 self.params = self.params_class.parse( | ||||
|                     request, self.provider, client_id, client_secret | ||||
|                 ) | ||||
|  | ||||
|             with start_span( | ||||
|                 op="authentik.providers.oauth2.post.response", | ||||
|  | ||||
| @ -61,6 +61,22 @@ class SessionMiddleware(UpstreamSessionMiddleware): | ||||
|             pass | ||||
|         return session_key | ||||
|  | ||||
|     @staticmethod | ||||
|     def encode_session(session_key: str, user: User): | ||||
|         payload = { | ||||
|             "sid": session_key, | ||||
|             "iss": "authentik", | ||||
|             "sub": "anonymous", | ||||
|             "authenticated": user.is_authenticated, | ||||
|             "acr": ACR_AUTHENTIK_SESSION, | ||||
|         } | ||||
|         if user.is_authenticated: | ||||
|             payload["sub"] = user.uid | ||||
|         value = encode(payload=payload, key=SIGNING_HASH) | ||||
|         if settings.TEST: | ||||
|             value = session_key | ||||
|         return value | ||||
|  | ||||
|     def process_request(self, request: HttpRequest): | ||||
|         raw_session = request.COOKIES.get(settings.SESSION_COOKIE_NAME) | ||||
|         session_key = SessionMiddleware.decode_session_key(raw_session) | ||||
| @ -117,21 +133,9 @@ class SessionMiddleware(UpstreamSessionMiddleware): | ||||
|                             "request completed. The user may have logged " | ||||
|                             "out in a concurrent request, for example." | ||||
|                         ) from None | ||||
|                     payload = { | ||||
|                         "sid": request.session.session_key, | ||||
|                         "iss": "authentik", | ||||
|                         "sub": "anonymous", | ||||
|                         "authenticated": request.user.is_authenticated, | ||||
|                         "acr": ACR_AUTHENTIK_SESSION, | ||||
|                     } | ||||
|                     if request.user.is_authenticated: | ||||
|                         payload["sub"] = request.user.uid | ||||
|                     value = encode(payload=payload, key=SIGNING_HASH) | ||||
|                     if settings.TEST: | ||||
|                         value = request.session.session_key | ||||
|                     response.set_cookie( | ||||
|                         settings.SESSION_COOKIE_NAME, | ||||
|                         value, | ||||
|                         SessionMiddleware.encode_session(request.session.session_key, request.user), | ||||
|                         max_age=max_age, | ||||
|                         expires=expires, | ||||
|                         domain=settings.SESSION_COOKIE_DOMAIN, | ||||
|  | ||||
| @ -496,6 +496,46 @@ | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     "type": "object", | ||||
|                     "required": [ | ||||
|                         "model", | ||||
|                         "identifiers" | ||||
|                     ], | ||||
|                     "properties": { | ||||
|                         "model": { | ||||
|                             "const": "authentik_providers_apple_psso.appleplatformssoprovider" | ||||
|                         }, | ||||
|                         "id": { | ||||
|                             "type": "string" | ||||
|                         }, | ||||
|                         "state": { | ||||
|                             "type": "string", | ||||
|                             "enum": [ | ||||
|                                 "absent", | ||||
|                                 "created", | ||||
|                                 "must_created", | ||||
|                                 "present" | ||||
|                             ], | ||||
|                             "default": "present" | ||||
|                         }, | ||||
|                         "conditions": { | ||||
|                             "type": "array", | ||||
|                             "items": { | ||||
|                                 "type": "boolean" | ||||
|                             } | ||||
|                         }, | ||||
|                         "permissions": { | ||||
|                             "$ref": "#/$defs/model_authentik_providers_apple_psso.appleplatformssoprovider_permissions" | ||||
|                         }, | ||||
|                         "attrs": { | ||||
|                             "$ref": "#/$defs/model_authentik_providers_apple_psso.appleplatformssoprovider" | ||||
|                         }, | ||||
|                         "identifiers": { | ||||
|                             "$ref": "#/$defs/model_authentik_providers_apple_psso.appleplatformssoprovider" | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     "type": "object", | ||||
|                     "required": [ | ||||
| @ -5028,6 +5068,22 @@ | ||||
|                             "authentik_policies_unique_password.delete_userpasswordhistory", | ||||
|                             "authentik_policies_unique_password.view_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.view_userpasswordhistory", | ||||
|                             "authentik_providers_apple_psso.add_appledevice", | ||||
|                             "authentik_providers_apple_psso.add_appledeviceuser", | ||||
|                             "authentik_providers_apple_psso.add_applenonce", | ||||
|                             "authentik_providers_apple_psso.add_appleplatformssoprovider", | ||||
|                             "authentik_providers_apple_psso.change_appledevice", | ||||
|                             "authentik_providers_apple_psso.change_appledeviceuser", | ||||
|                             "authentik_providers_apple_psso.change_applenonce", | ||||
|                             "authentik_providers_apple_psso.change_appleplatformssoprovider", | ||||
|                             "authentik_providers_apple_psso.delete_appledevice", | ||||
|                             "authentik_providers_apple_psso.delete_appledeviceuser", | ||||
|                             "authentik_providers_apple_psso.delete_applenonce", | ||||
|                             "authentik_providers_apple_psso.delete_appleplatformssoprovider", | ||||
|                             "authentik_providers_apple_psso.view_appledevice", | ||||
|                             "authentik_providers_apple_psso.view_appledeviceuser", | ||||
|                             "authentik_providers_apple_psso.view_applenonce", | ||||
|                             "authentik_providers_apple_psso.view_appleplatformssoprovider", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovider", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidergroup", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidermapping", | ||||
| @ -5599,6 +5655,43 @@ | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "model_authentik_providers_apple_psso.appleplatformssoprovider": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "name": { | ||||
|                     "type": "string", | ||||
|                     "minLength": 1, | ||||
|                     "title": "Name" | ||||
|                 } | ||||
|             }, | ||||
|             "required": [] | ||||
|         }, | ||||
|         "model_authentik_providers_apple_psso.appleplatformssoprovider_permissions": { | ||||
|             "type": "array", | ||||
|             "items": { | ||||
|                 "type": "object", | ||||
|                 "required": [ | ||||
|                     "permission" | ||||
|                 ], | ||||
|                 "properties": { | ||||
|                     "permission": { | ||||
|                         "type": "string", | ||||
|                         "enum": [ | ||||
|                             "add_appleplatformssoprovider", | ||||
|                             "change_appleplatformssoprovider", | ||||
|                             "delete_appleplatformssoprovider", | ||||
|                             "view_appleplatformssoprovider" | ||||
|                         ] | ||||
|                     }, | ||||
|                     "user": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "role": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "model_authentik_providers_google_workspace.googleworkspaceprovider": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
| @ -7342,6 +7435,7 @@ | ||||
|                         "authentik.enterprise", | ||||
|                         "authentik.enterprise.audit", | ||||
|                         "authentik.enterprise.policies.unique_password", | ||||
|                         "authentik.enterprise.providers.apple_psso", | ||||
|                         "authentik.enterprise.providers.google_workspace", | ||||
|                         "authentik.enterprise.providers.microsoft_entra", | ||||
|                         "authentik.enterprise.providers.ssf", | ||||
| @ -7452,6 +7546,7 @@ | ||||
|                         "authentik_core.token", | ||||
|                         "authentik_enterprise.license", | ||||
|                         "authentik_policies_unique_password.uniquepasswordpolicy", | ||||
|                         "authentik_providers_apple_psso.appleplatformssoprovider", | ||||
|                         "authentik_providers_google_workspace.googleworkspaceprovider", | ||||
|                         "authentik_providers_google_workspace.googleworkspaceprovidermapping", | ||||
|                         "authentik_providers_microsoft_entra.microsoftentraprovider", | ||||
| @ -9674,6 +9769,22 @@ | ||||
|                             "authentik_policies_unique_password.delete_userpasswordhistory", | ||||
|                             "authentik_policies_unique_password.view_uniquepasswordpolicy", | ||||
|                             "authentik_policies_unique_password.view_userpasswordhistory", | ||||
|                             "authentik_providers_apple_psso.add_appledevice", | ||||
|                             "authentik_providers_apple_psso.add_appledeviceuser", | ||||
|                             "authentik_providers_apple_psso.add_applenonce", | ||||
|                             "authentik_providers_apple_psso.add_appleplatformssoprovider", | ||||
|                             "authentik_providers_apple_psso.change_appledevice", | ||||
|                             "authentik_providers_apple_psso.change_appledeviceuser", | ||||
|                             "authentik_providers_apple_psso.change_applenonce", | ||||
|                             "authentik_providers_apple_psso.change_appleplatformssoprovider", | ||||
|                             "authentik_providers_apple_psso.delete_appledevice", | ||||
|                             "authentik_providers_apple_psso.delete_appledeviceuser", | ||||
|                             "authentik_providers_apple_psso.delete_applenonce", | ||||
|                             "authentik_providers_apple_psso.delete_appleplatformssoprovider", | ||||
|                             "authentik_providers_apple_psso.view_appledevice", | ||||
|                             "authentik_providers_apple_psso.view_appledeviceuser", | ||||
|                             "authentik_providers_apple_psso.view_applenonce", | ||||
|                             "authentik_providers_apple_psso.view_appleplatformssoprovider", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovider", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidergroup", | ||||
|                             "authentik_providers_google_workspace.add_googleworkspaceprovidermapping", | ||||
|  | ||||
							
								
								
									
										16
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								schema.yml
									
									
									
									
									
								
							| @ -24864,6 +24864,7 @@ paths: | ||||
|           - authentik_policies_password.passwordpolicy | ||||
|           - authentik_policies_reputation.reputationpolicy | ||||
|           - authentik_policies_unique_password.uniquepasswordpolicy | ||||
|           - authentik_providers_apple_psso.appleplatformssoprovider | ||||
|           - authentik_providers_google_workspace.googleworkspaceprovider | ||||
|           - authentik_providers_google_workspace.googleworkspaceprovidermapping | ||||
|           - authentik_providers_ldap.ldapprovider | ||||
| @ -25113,6 +25114,7 @@ paths: | ||||
|           - authentik_policies_password.passwordpolicy | ||||
|           - authentik_policies_reputation.reputationpolicy | ||||
|           - authentik_policies_unique_password.uniquepasswordpolicy | ||||
|           - authentik_providers_apple_psso.appleplatformssoprovider | ||||
|           - authentik_providers_google_workspace.googleworkspaceprovider | ||||
|           - authentik_providers_google_workspace.googleworkspaceprovidermapping | ||||
|           - authentik_providers_ldap.ldapprovider | ||||
| @ -41212,6 +41214,7 @@ components: | ||||
|       - authentik.enterprise | ||||
|       - authentik.enterprise.audit | ||||
|       - authentik.enterprise.policies.unique_password | ||||
|       - authentik.enterprise.providers.apple_psso | ||||
|       - authentik.enterprise.providers.google_workspace | ||||
|       - authentik.enterprise.providers.microsoft_entra | ||||
|       - authentik.enterprise.providers.ssf | ||||
| @ -41258,6 +41261,15 @@ components: | ||||
|       - redirect_uri | ||||
|       - scope | ||||
|       - state | ||||
|     ApplePlatformSSOProviderRequest: | ||||
|       type: object | ||||
|       description: ApplePlatformSSOProvider Serializer | ||||
|       properties: | ||||
|         name: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|       required: | ||||
|       - name | ||||
|     Application: | ||||
|       type: object | ||||
|       description: Application Serializer | ||||
| @ -48714,6 +48726,7 @@ components: | ||||
|       - authentik_core.token | ||||
|       - authentik_enterprise.license | ||||
|       - authentik_policies_unique_password.uniquepasswordpolicy | ||||
|       - authentik_providers_apple_psso.appleplatformssoprovider | ||||
|       - authentik_providers_google_workspace.googleworkspaceprovider | ||||
|       - authentik_providers_google_workspace.googleworkspaceprovidermapping | ||||
|       - authentik_providers_microsoft_entra.microsoftentraprovider | ||||
| @ -56659,6 +56672,7 @@ components: | ||||
|       type: string | ||||
|     ProviderModelEnum: | ||||
|       enum: | ||||
|       - authentik_providers_apple_psso.appleplatformssoprovider | ||||
|       - authentik_providers_google_workspace.googleworkspaceprovider | ||||
|       - authentik_providers_ldap.ldapprovider | ||||
|       - authentik_providers_microsoft_entra.microsoftentraprovider | ||||
| @ -61871,6 +61885,7 @@ components: | ||||
|       - worker_id | ||||
|     modelRequest: | ||||
|       oneOf: | ||||
|       - $ref: '#/components/schemas/ApplePlatformSSOProviderRequest' | ||||
|       - $ref: '#/components/schemas/GoogleWorkspaceProviderRequest' | ||||
|       - $ref: '#/components/schemas/LDAPProviderRequest' | ||||
|       - $ref: '#/components/schemas/MicrosoftEntraProviderRequest' | ||||
| @ -61884,6 +61899,7 @@ components: | ||||
|       discriminator: | ||||
|         propertyName: provider_model | ||||
|         mapping: | ||||
|           authentik_providers_apple_psso.appleplatformssoprovider: '#/components/schemas/ApplePlatformSSOProviderRequest' | ||||
|           authentik_providers_google_workspace.googleworkspaceprovider: '#/components/schemas/GoogleWorkspaceProviderRequest' | ||||
|           authentik_providers_ldap.ldapprovider: '#/components/schemas/LDAPProviderRequest' | ||||
|           authentik_providers_microsoft_entra.microsoftentraprovider: '#/components/schemas/MicrosoftEntraProviderRequest' | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	