authentik.admin.files.validation
1import re 2from pathlib import PurePosixPath 3 4from django.core.exceptions import ValidationError 5from django.utils.translation import gettext as _ 6 7from authentik.admin.files.backends.base import THEME_VARIABLE 8from authentik.admin.files.backends.passthrough import PassthroughBackend 9from authentik.admin.files.backends.static import StaticBackend 10from authentik.admin.files.usage import FileUsage 11 12# File upload limits 13MAX_FILE_NAME_LENGTH = 1024 14MAX_PATH_COMPONENT_LENGTH = 255 15 16 17def validate_file_name(name: str) -> None: 18 if PassthroughBackend(FileUsage.MEDIA).supports_file(name) or StaticBackend( 19 FileUsage.MEDIA 20 ).supports_file(name): 21 return 22 validate_upload_file_name(name) 23 24 25def validate_upload_file_name( 26 name: str, 27 ValidationError: type[Exception] = ValidationError, 28) -> None: 29 """Sanitize file path. 30 31 Args: 32 file_path: The file path to sanitize 33 34 Returns: 35 Sanitized file path 36 37 Raises: 38 ValidationError: If file path is invalid 39 """ 40 if not name: 41 raise ValidationError(_("File name cannot be empty")) 42 43 # Allow %(theme)s placeholder for theme-specific files 44 # Replace with placeholder for validation, then check the result 45 name_for_validation = name.replace(THEME_VARIABLE, "theme") 46 47 # Same regex is used in the frontend as well (with %(theme)s handling) 48 if not re.match(r"^[a-zA-Z0-9._/-]+$", name_for_validation): 49 raise ValidationError( 50 _( 51 "File name can only contain letters (a-z, A-Z), numbers (0-9), " 52 "dots (.), hyphens (-), underscores (_), forward slashes (/), " 53 "and the placeholder %(theme)s for theme-specific files" 54 ) 55 ) 56 57 if "//" in name: 58 raise ValidationError(_("File name cannot contain duplicate /")) 59 60 # Convert to posix path 61 path = PurePosixPath(name) 62 63 # Check for absolute paths 64 # Needs the / at the start. If it doesn't have it, it might still be unsafe, so see L53+ 65 if path.is_absolute(): 66 raise ValidationError(_("Absolute paths are not allowed")) 67 68 # Check for parent directory references 69 if ".." in path.parts: 70 raise ValidationError(_("Parent directory references ('..') are not allowed")) 71 72 # Disallow paths starting with dot (hidden files at root level) 73 if str(path).startswith("."): 74 raise ValidationError(_("Paths cannot start with '.'")) 75 76 # Check path length limits 77 normalized = str(path) 78 if len(normalized) > MAX_FILE_NAME_LENGTH: 79 raise ValidationError(_(f"File name too long (max {MAX_FILE_NAME_LENGTH} characters)")) 80 81 for part in path.parts: 82 if len(part) > MAX_PATH_COMPONENT_LENGTH: 83 raise ValidationError( 84 _(f"Path component too long (max {MAX_PATH_COMPONENT_LENGTH} characters)") 85 )
MAX_FILE_NAME_LENGTH =
1024
MAX_PATH_COMPONENT_LENGTH =
255
def
validate_file_name(name: str) -> None:
def
validate_upload_file_name( name: str, ValidationError: type[Exception] = <class 'django.core.exceptions.ValidationError'>) -> None:
26def validate_upload_file_name( 27 name: str, 28 ValidationError: type[Exception] = ValidationError, 29) -> None: 30 """Sanitize file path. 31 32 Args: 33 file_path: The file path to sanitize 34 35 Returns: 36 Sanitized file path 37 38 Raises: 39 ValidationError: If file path is invalid 40 """ 41 if not name: 42 raise ValidationError(_("File name cannot be empty")) 43 44 # Allow %(theme)s placeholder for theme-specific files 45 # Replace with placeholder for validation, then check the result 46 name_for_validation = name.replace(THEME_VARIABLE, "theme") 47 48 # Same regex is used in the frontend as well (with %(theme)s handling) 49 if not re.match(r"^[a-zA-Z0-9._/-]+$", name_for_validation): 50 raise ValidationError( 51 _( 52 "File name can only contain letters (a-z, A-Z), numbers (0-9), " 53 "dots (.), hyphens (-), underscores (_), forward slashes (/), " 54 "and the placeholder %(theme)s for theme-specific files" 55 ) 56 ) 57 58 if "//" in name: 59 raise ValidationError(_("File name cannot contain duplicate /")) 60 61 # Convert to posix path 62 path = PurePosixPath(name) 63 64 # Check for absolute paths 65 # Needs the / at the start. If it doesn't have it, it might still be unsafe, so see L53+ 66 if path.is_absolute(): 67 raise ValidationError(_("Absolute paths are not allowed")) 68 69 # Check for parent directory references 70 if ".." in path.parts: 71 raise ValidationError(_("Parent directory references ('..') are not allowed")) 72 73 # Disallow paths starting with dot (hidden files at root level) 74 if str(path).startswith("."): 75 raise ValidationError(_("Paths cannot start with '.'")) 76 77 # Check path length limits 78 normalized = str(path) 79 if len(normalized) > MAX_FILE_NAME_LENGTH: 80 raise ValidationError(_(f"File name too long (max {MAX_FILE_NAME_LENGTH} characters)")) 81 82 for part in path.parts: 83 if len(part) > MAX_PATH_COMPONENT_LENGTH: 84 raise ValidationError( 85 _(f"Path component too long (max {MAX_PATH_COMPONENT_LENGTH} characters)") 86 )
Sanitize file path.
Args: file_path: The file path to sanitize
Returns: Sanitized file path
Raises: ValidationError: If file path is invalid