authentik.brands.api
Serializer for brands models
1"""Serializer for brands models""" 2 3from typing import Any 4 5from django.db import models 6from drf_spectacular.utils import extend_schema, extend_schema_field 7from rest_framework.decorators import action 8from rest_framework.exceptions import ValidationError 9from rest_framework.fields import ( 10 CharField, 11 ChoiceField, 12 ListField, 13 SerializerMethodField, 14) 15from rest_framework.filters import OrderingFilter, SearchFilter 16from rest_framework.permissions import AllowAny 17from rest_framework.request import Request 18from rest_framework.response import Response 19from rest_framework.validators import UniqueValidator 20from rest_framework.viewsets import ModelViewSet 21 22from authentik.brands.models import Brand 23from authentik.core.api.used_by import UsedByMixin 24from authentik.core.api.utils import ModelSerializer, PassiveSerializer, ThemedUrlsSerializer 25from authentik.rbac.filters import SecretKeyFilter 26from authentik.tenants.api.settings import FlagJSONField 27from authentik.tenants.flags import Flag 28from authentik.tenants.utils import get_current_tenant 29 30 31class FooterLinkSerializer(PassiveSerializer): 32 """Links returned in Config API""" 33 34 href = CharField(read_only=True, allow_null=True) 35 name = CharField(read_only=True) 36 37 38class BrandSerializer(ModelSerializer): 39 """Brand Serializer""" 40 41 def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: 42 if attrs.get("default", False): 43 brands = Brand.objects.filter(default=True) 44 if self.instance: 45 brands = brands.exclude(pk=self.instance.pk) 46 if brands.exists(): 47 raise ValidationError({"default": "Only a single brand can be set as default."}) 48 return super().validate(attrs) 49 50 class Meta: 51 model = Brand 52 fields = [ 53 "brand_uuid", 54 "domain", 55 "default", 56 "branding_title", 57 "branding_logo", 58 "branding_favicon", 59 "branding_custom_css", 60 "branding_default_flow_background", 61 "flow_authentication", 62 "flow_invalidation", 63 "flow_recovery", 64 "flow_unenrollment", 65 "flow_user_settings", 66 "flow_device_code", 67 "flow_lockdown", 68 "default_application", 69 "web_certificate", 70 "client_certificates", 71 "attributes", 72 ] 73 extra_kwargs = { 74 # TODO: This field isn't unique on the database which is hard to backport 75 # hence we just validate the uniqueness here 76 "domain": {"validators": [UniqueValidator(Brand.objects.all())]}, 77 } 78 79 80class Themes(models.TextChoices): 81 """Themes""" 82 83 AUTOMATIC = "automatic" 84 LIGHT = "light" 85 DARK = "dark" 86 87 88def get_default_ui_footer_links(): 89 """Get default UI footer links based on current tenant settings""" 90 return get_current_tenant().footer_links 91 92 93class CurrentBrandSerializer(PassiveSerializer): 94 """Partial brand information for styling""" 95 96 matched_domain = CharField(source="domain") 97 branding_title = CharField() 98 branding_logo = CharField(source="branding_logo_url") 99 branding_logo_themed_urls = ThemedUrlsSerializer(read_only=True, allow_null=True) 100 branding_favicon = CharField(source="branding_favicon_url") 101 branding_favicon_themed_urls = ThemedUrlsSerializer(read_only=True, allow_null=True) 102 branding_custom_css = CharField() 103 ui_footer_links = ListField( 104 child=FooterLinkSerializer(), 105 read_only=True, 106 default=get_default_ui_footer_links, 107 ) 108 ui_theme = ChoiceField( 109 choices=Themes.choices, 110 source="attributes.settings.theme.base", 111 default=Themes.AUTOMATIC, 112 read_only=True, 113 ) 114 115 flow_authentication = CharField(source="flow_authentication.slug", required=False) 116 flow_invalidation = CharField(source="flow_invalidation.slug", required=False) 117 flow_recovery = CharField(source="flow_recovery.slug", required=False) 118 flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) 119 flow_user_settings = CharField(source="flow_user_settings.slug", required=False) 120 flow_device_code = CharField(source="flow_device_code.slug", required=False) 121 flow_lockdown = CharField(source="flow_lockdown.slug", required=False) 122 123 default_locale = CharField(read_only=True) 124 flags = SerializerMethodField() 125 126 @extend_schema_field(field=FlagJSONField) 127 def get_flags(self, _): 128 values = {} 129 for flag in Flag.available(visibility="public"): 130 values[flag().key] = flag.get() 131 return values 132 133 134class BrandViewSet(UsedByMixin, ModelViewSet): 135 """Brand Viewset""" 136 137 queryset = Brand.objects.all() 138 serializer_class = BrandSerializer 139 search_fields = [ 140 "domain", 141 "branding_title", 142 "web_certificate__name", 143 "client_certificates__name", 144 ] 145 filterset_fields = [ 146 "brand_uuid", 147 "domain", 148 "default", 149 "branding_title", 150 "branding_logo", 151 "branding_favicon", 152 "branding_default_flow_background", 153 "flow_authentication", 154 "flow_invalidation", 155 "flow_recovery", 156 "flow_unenrollment", 157 "flow_user_settings", 158 "flow_device_code", 159 "flow_lockdown", 160 "web_certificate", 161 "client_certificates", 162 ] 163 ordering = ["domain"] 164 165 filter_backends = [SecretKeyFilter, OrderingFilter, SearchFilter] 166 167 @extend_schema( 168 responses=CurrentBrandSerializer(many=False), 169 ) 170 @action(methods=["GET"], detail=False, permission_classes=[AllowAny]) 171 def current(self, request: Request) -> Response: 172 """Get current brand""" 173 brand: Brand = request._request.brand 174 return Response(CurrentBrandSerializer(brand, context={"request": request}).data)
39class BrandSerializer(ModelSerializer): 40 """Brand Serializer""" 41 42 def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: 43 if attrs.get("default", False): 44 brands = Brand.objects.filter(default=True) 45 if self.instance: 46 brands = brands.exclude(pk=self.instance.pk) 47 if brands.exists(): 48 raise ValidationError({"default": "Only a single brand can be set as default."}) 49 return super().validate(attrs) 50 51 class Meta: 52 model = Brand 53 fields = [ 54 "brand_uuid", 55 "domain", 56 "default", 57 "branding_title", 58 "branding_logo", 59 "branding_favicon", 60 "branding_custom_css", 61 "branding_default_flow_background", 62 "flow_authentication", 63 "flow_invalidation", 64 "flow_recovery", 65 "flow_unenrollment", 66 "flow_user_settings", 67 "flow_device_code", 68 "flow_lockdown", 69 "default_application", 70 "web_certificate", 71 "client_certificates", 72 "attributes", 73 ] 74 extra_kwargs = { 75 # TODO: This field isn't unique on the database which is hard to backport 76 # hence we just validate the uniqueness here 77 "domain": {"validators": [UniqueValidator(Brand.objects.all())]}, 78 }
Brand Serializer
def
validate(self, attrs: dict[str, typing.Any]) -> dict[str, typing.Any]:
42 def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: 43 if attrs.get("default", False): 44 brands = Brand.objects.filter(default=True) 45 if self.instance: 46 brands = brands.exclude(pk=self.instance.pk) 47 if brands.exists(): 48 raise ValidationError({"default": "Only a single brand can be set as default."}) 49 return super().validate(attrs)
Inherited Members
class
BrandSerializer.Meta:
51 class Meta: 52 model = Brand 53 fields = [ 54 "brand_uuid", 55 "domain", 56 "default", 57 "branding_title", 58 "branding_logo", 59 "branding_favicon", 60 "branding_custom_css", 61 "branding_default_flow_background", 62 "flow_authentication", 63 "flow_invalidation", 64 "flow_recovery", 65 "flow_unenrollment", 66 "flow_user_settings", 67 "flow_device_code", 68 "flow_lockdown", 69 "default_application", 70 "web_certificate", 71 "client_certificates", 72 "attributes", 73 ] 74 extra_kwargs = { 75 # TODO: This field isn't unique on the database which is hard to backport 76 # hence we just validate the uniqueness here 77 "domain": {"validators": [UniqueValidator(Brand.objects.all())]}, 78 }
model =
<class 'authentik.brands.models.Brand'>
fields =
['brand_uuid', 'domain', 'default', 'branding_title', 'branding_logo', 'branding_favicon', 'branding_custom_css', 'branding_default_flow_background', 'flow_authentication', 'flow_invalidation', 'flow_recovery', 'flow_unenrollment', 'flow_user_settings', 'flow_device_code', 'flow_lockdown', 'default_application', 'web_certificate', 'client_certificates', 'attributes']
class
Themes(django.db.models.enums.TextChoices):
81class Themes(models.TextChoices): 82 """Themes""" 83 84 AUTOMATIC = "automatic" 85 LIGHT = "light" 86 DARK = "dark"
Themes
AUTOMATIC =
Themes.AUTOMATIC
LIGHT =
Themes.LIGHT
DARK =
Themes.DARK
94class CurrentBrandSerializer(PassiveSerializer): 95 """Partial brand information for styling""" 96 97 matched_domain = CharField(source="domain") 98 branding_title = CharField() 99 branding_logo = CharField(source="branding_logo_url") 100 branding_logo_themed_urls = ThemedUrlsSerializer(read_only=True, allow_null=True) 101 branding_favicon = CharField(source="branding_favicon_url") 102 branding_favicon_themed_urls = ThemedUrlsSerializer(read_only=True, allow_null=True) 103 branding_custom_css = CharField() 104 ui_footer_links = ListField( 105 child=FooterLinkSerializer(), 106 read_only=True, 107 default=get_default_ui_footer_links, 108 ) 109 ui_theme = ChoiceField( 110 choices=Themes.choices, 111 source="attributes.settings.theme.base", 112 default=Themes.AUTOMATIC, 113 read_only=True, 114 ) 115 116 flow_authentication = CharField(source="flow_authentication.slug", required=False) 117 flow_invalidation = CharField(source="flow_invalidation.slug", required=False) 118 flow_recovery = CharField(source="flow_recovery.slug", required=False) 119 flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) 120 flow_user_settings = CharField(source="flow_user_settings.slug", required=False) 121 flow_device_code = CharField(source="flow_device_code.slug", required=False) 122 flow_lockdown = CharField(source="flow_lockdown.slug", required=False) 123 124 default_locale = CharField(read_only=True) 125 flags = SerializerMethodField() 126 127 @extend_schema_field(field=FlagJSONField) 128 def get_flags(self, _): 129 values = {} 130 for flag in Flag.available(visibility="public"): 131 values[flag().key] = flag.get() 132 return values
Partial brand information for styling
Inherited Members
135class BrandViewSet(UsedByMixin, ModelViewSet): 136 """Brand Viewset""" 137 138 queryset = Brand.objects.all() 139 serializer_class = BrandSerializer 140 search_fields = [ 141 "domain", 142 "branding_title", 143 "web_certificate__name", 144 "client_certificates__name", 145 ] 146 filterset_fields = [ 147 "brand_uuid", 148 "domain", 149 "default", 150 "branding_title", 151 "branding_logo", 152 "branding_favicon", 153 "branding_default_flow_background", 154 "flow_authentication", 155 "flow_invalidation", 156 "flow_recovery", 157 "flow_unenrollment", 158 "flow_user_settings", 159 "flow_device_code", 160 "flow_lockdown", 161 "web_certificate", 162 "client_certificates", 163 ] 164 ordering = ["domain"] 165 166 filter_backends = [SecretKeyFilter, OrderingFilter, SearchFilter] 167 168 @extend_schema( 169 responses=CurrentBrandSerializer(many=False), 170 ) 171 @action(methods=["GET"], detail=False, permission_classes=[AllowAny]) 172 def current(self, request: Request) -> Response: 173 """Get current brand""" 174 brand: Brand = request._request.brand 175 return Response(CurrentBrandSerializer(brand, context={"request": request}).data)
Brand Viewset
serializer_class =
<class 'BrandSerializer'>
filterset_fields =
['brand_uuid', 'domain', 'default', 'branding_title', 'branding_logo', 'branding_favicon', 'branding_default_flow_background', 'flow_authentication', 'flow_invalidation', 'flow_recovery', 'flow_unenrollment', 'flow_user_settings', 'flow_device_code', 'flow_lockdown', 'web_certificate', 'client_certificates']
filter_backends =
[<class 'authentik.rbac.filters.SecretKeyFilter'>, <class 'rest_framework.filters.OrderingFilter'>, <class 'rest_framework.filters.SearchFilter'>]
@extend_schema(responses=CurrentBrandSerializer(many=False))
@action(methods=['GET'], detail=False, permission_classes=[AllowAny])
def
current( self, request: rest_framework.request.Request) -> rest_framework.response.Response:
168 @extend_schema( 169 responses=CurrentBrandSerializer(many=False), 170 ) 171 @action(methods=["GET"], detail=False, permission_classes=[AllowAny]) 172 def current(self, request: Request) -> Response: 173 """Get current brand""" 174 brand: Brand = request._request.brand 175 return Response(CurrentBrandSerializer(brand, context={"request": request}).data)
Get current brand