core: better API validation for JSON fields (#15236)

* core: better API validation for JSON fields

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix web

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2025-06-25 15:05:32 +02:00
committed by GitHub
parent db1e8b291f
commit d4ca070d76
4 changed files with 171 additions and 66 deletions

View File

@ -2,6 +2,7 @@
from typing import Any
from django.db import models
from django.db.models import Model
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
from drf_spectacular.plumbing import build_basic_type
@ -30,7 +31,27 @@ def is_dict(value: Any):
raise ValidationError("Value must be a dictionary, and not have any duplicate keys.")
class JSONDictField(JSONField):
"""JSON Field which only allows dictionaries"""
default_validators = [is_dict]
class JSONExtension(OpenApiSerializerFieldExtension):
"""Generate API Schema for JSON fields as"""
target_class = "authentik.core.api.utils.JSONDictField"
def map_serializer_field(self, auto_schema, direction):
return build_basic_type(OpenApiTypes.OBJECT)
class ModelSerializer(BaseModelSerializer):
# By default, JSON fields we have are used to store dictionaries
serializer_field_mapping = BaseModelSerializer.serializer_field_mapping.copy()
serializer_field_mapping[models.JSONField] = JSONDictField
def create(self, validated_data):
instance = super().create(validated_data)
@ -71,21 +92,6 @@ class ModelSerializer(BaseModelSerializer):
return instance
class JSONDictField(JSONField):
"""JSON Field which only allows dictionaries"""
default_validators = [is_dict]
class JSONExtension(OpenApiSerializerFieldExtension):
"""Generate API Schema for JSON fields as"""
target_class = "authentik.core.api.utils.JSONDictField"
def map_serializer_field(self, auto_schema, direction):
return build_basic_type(OpenApiTypes.OBJECT)
class PassiveSerializer(Serializer):
"""Base serializer class which doesn't implement create/update methods"""

View File

@ -1,6 +1,7 @@
"""Serializer for tenants models"""
from django_tenants.utils import get_public_schema_name
from rest_framework.fields import JSONField
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.permissions import SAFE_METHODS
@ -12,6 +13,8 @@ from authentik.tenants.models import Tenant
class SettingsSerializer(ModelSerializer):
"""Settings Serializer"""
footer_links = JSONField(required=False)
class Meta:
model = Tenant
fields = [

View File

@ -41338,7 +41338,9 @@ components:
app:
type: string
format: uuid
attributes: {}
attributes:
type: object
additionalProperties: {}
required:
- app
- name
@ -41353,7 +41355,9 @@ components:
app:
type: string
format: uuid
attributes: {}
attributes:
type: object
additionalProperties: {}
required:
- app
- name
@ -41942,7 +41946,9 @@ components:
friendly_name:
type: string
nullable: true
credentials: {}
credentials:
type: object
additionalProperties: {}
required:
- component
- credentials
@ -41972,7 +41978,9 @@ components:
type: string
nullable: true
minLength: 1
credentials: {}
credentials:
type: object
additionalProperties: {}
required:
- credentials
- name
@ -42777,7 +42785,9 @@ components:
path:
type: string
default: ''
context: {}
context:
type: object
additionalProperties: {}
last_applied:
type: string
format: date-time
@ -42797,6 +42807,8 @@ components:
type: string
readOnly: true
metadata:
type: object
additionalProperties: {}
readOnly: true
content:
type: string
@ -42818,7 +42830,9 @@ components:
path:
type: string
default: ''
context: {}
context:
type: object
additionalProperties: {}
enabled:
type: boolean
content:
@ -42898,7 +42912,9 @@ components:
type: string
format: uuid
description: Certificates used for client authentication.
attributes: {}
attributes:
type: object
additionalProperties: {}
required:
- brand_uuid
- domain
@ -42968,7 +42984,9 @@ components:
type: string
format: uuid
description: Certificates used for client authentication.
attributes: {}
attributes:
type: object
additionalProperties: {}
required:
- domain
Cache:
@ -44609,7 +44627,9 @@ components:
$ref: '#/components/schemas/ProtocolEnum'
host:
type: string
settings: {}
settings:
type: object
additionalProperties: {}
property_mappings:
type: array
items:
@ -44680,7 +44700,9 @@ components:
host:
type: string
minLength: 1
settings: {}
settings:
type: object
additionalProperties: {}
property_mappings:
type: array
items:
@ -44744,12 +44766,16 @@ components:
format: uuid
readOnly: true
title: Event uuid
user: {}
user:
type: object
additionalProperties: {}
action:
$ref: '#/components/schemas/EventActions'
app:
type: string
context: {}
context:
type: object
additionalProperties: {}
client_ip:
type: string
nullable: true
@ -44760,7 +44786,9 @@ components:
expires:
type: string
format: date-time
brand: {}
brand:
type: object
additionalProperties: {}
required:
- action
- app
@ -44905,13 +44933,17 @@ components:
type: object
description: Event Serializer
properties:
user: {}
user:
type: object
additionalProperties: {}
action:
$ref: '#/components/schemas/EventActions'
app:
type: string
minLength: 1
context: {}
context:
type: object
additionalProperties: {}
client_ip:
type: string
nullable: true
@ -44919,7 +44951,9 @@ components:
expires:
type: string
format: date-time
brand: {}
brand:
type: object
additionalProperties: {}
required:
- action
- app
@ -45894,7 +45928,9 @@ components:
type: string
format: email
maxLength: 254
credentials: {}
credentials:
type: object
additionalProperties: {}
scopes:
type: string
exclude_users_service_account:
@ -45945,6 +45981,8 @@ components:
provider:
type: integer
attributes:
type: object
additionalProperties: {}
readOnly: true
required:
- attributes
@ -46059,7 +46097,9 @@ components:
format: email
minLength: 1
maxLength: 254
credentials: {}
credentials:
type: object
additionalProperties: {}
scopes:
type: string
minLength: 1
@ -46104,6 +46144,8 @@ components:
provider:
type: integer
attributes:
type: object
additionalProperties: {}
readOnly: true
required:
- attributes
@ -47432,6 +47474,8 @@ components:
description: Return internal model name
readOnly: true
kubeconfig:
type: object
additionalProperties: {}
description: Paste your kubeconfig here. authentik will automatically use
the currently selected context.
verify_ssl:
@ -47456,6 +47500,8 @@ components:
description: If enabled, use the local connection. Required Docker socket/Kubernetes
Integration
kubeconfig:
type: object
additionalProperties: {}
description: Paste your kubeconfig here. authentik will automatically use
the currently selected context.
verify_ssl:
@ -48392,6 +48438,8 @@ components:
provider:
type: integer
attributes:
type: object
additionalProperties: {}
readOnly: true
required:
- attributes
@ -48548,6 +48596,8 @@ components:
provider:
type: integer
attributes:
type: object
additionalProperties: {}
readOnly: true
required:
- attributes
@ -49460,7 +49510,9 @@ components:
type: string
oidc_jwks_url:
type: string
oidc_jwks: {}
oidc_jwks:
type: object
additionalProperties: {}
authorization_code_auth_method:
allOf:
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
@ -49634,7 +49686,9 @@ components:
type: string
oidc_jwks_url:
type: string
oidc_jwks: {}
oidc_jwks:
type: object
additionalProperties: {}
authorization_code_auth_method:
allOf:
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
@ -52319,7 +52373,9 @@ components:
app:
type: string
format: uuid
attributes: {}
attributes:
type: object
additionalProperties: {}
PatchedApplicationRequest:
type: object
description: Application Serializer
@ -52471,7 +52527,9 @@ components:
type: string
nullable: true
minLength: 1
credentials: {}
credentials:
type: object
additionalProperties: {}
PatchedAuthenticatorSMSStageRequest:
type: object
description: AuthenticatorSMSStage Serializer
@ -52658,7 +52716,9 @@ components:
path:
type: string
default: ''
context: {}
context:
type: object
additionalProperties: {}
enabled:
type: boolean
content:
@ -52729,7 +52789,9 @@ components:
type: string
format: uuid
description: Certificates used for client authentication.
attributes: {}
attributes:
type: object
additionalProperties: {}
PatchedCaptchaStageRequest:
type: object
description: CaptchaStage Serializer
@ -53005,7 +53067,9 @@ components:
host:
type: string
minLength: 1
settings: {}
settings:
type: object
additionalProperties: {}
property_mappings:
type: array
items:
@ -53057,13 +53121,17 @@ components:
type: object
description: Event Serializer
properties:
user: {}
user:
type: object
additionalProperties: {}
action:
$ref: '#/components/schemas/EventActions'
app:
type: string
minLength: 1
context: {}
context:
type: object
additionalProperties: {}
client_ip:
type: string
nullable: true
@ -53071,7 +53139,9 @@ components:
expires:
type: string
format: date-time
brand: {}
brand:
type: object
additionalProperties: {}
PatchedExpressionPolicyRequest:
type: object
description: Group Membership Policy Serializer
@ -53254,7 +53324,9 @@ components:
format: email
minLength: 1
maxLength: 254
credentials: {}
credentials:
type: object
additionalProperties: {}
scopes:
type: string
minLength: 1
@ -53638,6 +53710,8 @@ components:
description: If enabled, use the local connection. Required Docker socket/Kubernetes
Integration
kubeconfig:
type: object
additionalProperties: {}
description: Paste your kubeconfig here. authentik will automatically use
the currently selected context.
verify_ssl:
@ -54221,7 +54295,9 @@ components:
type: string
oidc_jwks_url:
type: string
oidc_jwks: {}
oidc_jwks:
type: object
additionalProperties: {}
authorization_code_auth_method:
allOf:
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
@ -54700,7 +54776,9 @@ components:
items:
type: string
format: uuid
settings: {}
settings:
type: object
additionalProperties: {}
connection_expiry:
type: string
minLength: 1
@ -55157,7 +55235,9 @@ components:
source:
type: string
format: uuid
attributes: {}
attributes:
type: object
additionalProperties: {}
PatchedSCIMSourcePropertyMappingRequest:
type: object
description: SCIMSourcePropertyMapping Serializer
@ -55218,7 +55298,9 @@ components:
source:
type: string
format: uuid
attributes: {}
attributes:
type: object
additionalProperties: {}
PatchedSMSDeviceRequest:
type: object
description: Serializer for sms authenticator devices
@ -55305,9 +55387,7 @@ components:
minimum: 0
description: Reputation cannot increase higher than this value. Zero or
positive.
footer_links:
description: The option configures the footer links on the flow executor
pages.
footer_links: {}
gdpr_compliance:
type: boolean
description: When enabled, all the events caused by a user will be deleted
@ -57119,7 +57199,9 @@ components:
type: string
description: Return internal model name
readOnly: true
settings: {}
settings:
type: object
additionalProperties: {}
outpost_set:
type: array
items:
@ -57167,7 +57249,9 @@ components:
items:
type: string
format: uuid
settings: {}
settings:
type: object
additionalProperties: {}
connection_expiry:
type: string
minLength: 1
@ -57577,8 +57661,12 @@ components:
type: string
ip:
type: string
ip_geo_data: {}
ip_asn_data: {}
ip_geo_data:
type: object
additionalProperties: {}
ip_asn_data:
type: object
additionalProperties: {}
score:
type: integer
maximum: 9223372036854775807
@ -58651,6 +58739,8 @@ components:
provider:
type: integer
attributes:
type: object
additionalProperties: {}
readOnly: true
required:
- attributes
@ -58741,6 +58831,8 @@ components:
provider:
type: integer
attributes:
type: object
additionalProperties: {}
readOnly: true
required:
- attributes
@ -58855,7 +58947,9 @@ components:
source:
type: string
format: uuid
attributes: {}
attributes:
type: object
additionalProperties: {}
required:
- group
- group_obj
@ -58874,7 +58968,9 @@ components:
source:
type: string
format: uuid
attributes: {}
attributes:
type: object
additionalProperties: {}
required:
- group
- id
@ -58993,7 +59089,9 @@ components:
source:
type: string
format: uuid
attributes: {}
attributes:
type: object
additionalProperties: {}
required:
- id
- source
@ -59011,7 +59109,9 @@ components:
source:
type: string
format: uuid
attributes: {}
attributes:
type: object
additionalProperties: {}
required:
- id
- source
@ -59404,9 +59504,7 @@ components:
minimum: 0
description: Reputation cannot increase higher than this value. Zero or
positive.
footer_links:
description: The option configures the footer links on the flow executor
pages.
footer_links: {}
gdpr_compliance:
type: boolean
description: When enabled, all the events caused by a user will be deleted
@ -59458,9 +59556,7 @@ components:
minimum: 0
description: Reputation cannot increase higher than this value. Zero or
positive.
footer_links:
description: The option configures the footer links on the flow executor
pages.
footer_links: {}
gdpr_compliance:
type: boolean
description: When enabled, all the events caused by a user will be deleted

View File

@ -92,7 +92,7 @@ export class EventMap extends AKElement {
// Re-add them
this.events?.results
.filter((event) => {
if (!Object.hasOwn(event.context, "geo")) {
if (!Object.hasOwn(event.context || {}, "geo")) {
return false;
}
const geo = (event as EventWithContext).context.geo;