from rest_framework import serializers from infrasynth.shared.settings_utils import get_setting from .models import APIKey, Grant, Permission, Revoke, Role from .services import AuthorizationService class PermissionSerializer(serializers.ModelSerializer): class Meta: model = Permission fields = [ "id", "codename", "name", "app_label", "model", "action", "group", "description", "is_custom", "is_active", ] read_only_fields = fields class RoleSerializer(serializers.ModelSerializer): class Meta: model = Role fields = ["id", "name", "slug", "description", "permissions", "is_system", "users"] read_only_fields = ["id", "is_system"] def validate_permissions(self, value): if not get_setting("INFRASYNTH_SECURITY", "STRICT_PERMISSION_VALIDATION", False): return value known = set(Permission.objects.filter(is_active=True).values_list("codename", flat=True)) unknown = sorted({codename for codename in value if codename not in known}) if unknown: raise serializers.ValidationError(f"Unknown permission(s): {', '.join(unknown)}") return value def _validate_grant_scope(scope, request): if scope != "global": return user = getattr(request, "user", None) allowed = bool(user and getattr(user, "is_authenticated", False)) and ( getattr(user, "is_superuser", False) or AuthorizationService().has_permission(user, "platform.roles.manage") ) if not allowed: from rest_framework.exceptions import PermissionDenied raise PermissionDenied("Global grants/revokes require platform.roles.manage.") class GrantSerializer(serializers.ModelSerializer): scope = serializers.ChoiceField(choices=["tenant", "global"], default="tenant", write_only=True) class Meta: model = Grant fields = ["id", "user", "codename", "granted_by", "reason", "expires_at", "scope"] read_only_fields = ["id", "granted_by"] def validate(self, attrs): _validate_grant_scope(attrs.get("scope", "tenant"), self.context.get("request")) return attrs def create(self, validated_data): force_global = validated_data.pop("scope", "tenant") == "global" grant = Grant(**validated_data) if force_global: grant.tenant = None grant.save(force_global=True) else: grant.save() return grant class RevokeSerializer(serializers.ModelSerializer): scope = serializers.ChoiceField(choices=["tenant", "global"], default="tenant", write_only=True) class Meta: model = Revoke fields = ["id", "user", "codename", "revoked_by", "reason", "scope"] read_only_fields = ["id", "revoked_by"] def validate(self, attrs): _validate_grant_scope(attrs.get("scope", "tenant"), self.context.get("request")) return attrs def create(self, validated_data): force_global = validated_data.pop("scope", "tenant") == "global" revoke = Revoke(**validated_data) if force_global: revoke.tenant = None revoke.save(force_global=True) else: revoke.save() return revoke class APIKeySerializer(serializers.Serializer): id = serializers.IntegerField(read_only=True) name = serializers.CharField() prefix = serializers.CharField(read_only=True) key = serializers.CharField(read_only=True, help_text="Full secret, only shown once on creation") scopes = serializers.ListField(child=serializers.CharField(), default=list) is_active = serializers.BooleanField(default=True) expires_at = serializers.DateTimeField(allow_null=True, required=False) last_used_at = serializers.DateTimeField(read_only=True) def create(self, validated_data): return APIKey.objects.create(**validated_data) def update(self, instance, validated_data): for field, value in validated_data.items(): setattr(instance, field, value) instance.save() return instance class LoginSerializer(serializers.Serializer): username = serializers.CharField() password = serializers.CharField(write_only=True) class TwoFactorSetupSerializer(serializers.Serializer): secret = serializers.CharField(read_only=True) qr_base64 = serializers.CharField(read_only=True) provisioning_uri = serializers.CharField(read_only=True) class TwoFactorVerifySerializer(serializers.Serializer): code = serializers.CharField(max_length=10) secret = serializers.CharField(required=False) class TwoFactorRecoverySerializer(serializers.Serializer): recovery_code = serializers.CharField()