authentik.admin.files.manager

  1from collections.abc import Generator, Iterator
  2
  3from django.core.exceptions import ImproperlyConfigured
  4from django.http.request import HttpRequest
  5from rest_framework.request import Request
  6from structlog.stdlib import get_logger
  7
  8from authentik.admin.files.backends.base import ManageableBackend
  9from authentik.admin.files.backends.file import FileBackend
 10from authentik.admin.files.backends.passthrough import PassthroughBackend
 11from authentik.admin.files.backends.s3 import S3Backend
 12from authentik.admin.files.backends.static import StaticBackend
 13from authentik.admin.files.usage import FileUsage
 14from authentik.lib.config import CONFIG
 15
 16LOGGER = get_logger()
 17
 18
 19_FILE_BACKENDS = [
 20    StaticBackend,
 21    PassthroughBackend,
 22    FileBackend,
 23    S3Backend,
 24]
 25
 26
 27class FileManager:
 28    def __init__(self, usage: FileUsage) -> None:
 29        management_backend_name = CONFIG.get(
 30            f"storage.{usage.value}.backend",
 31            CONFIG.get("storage.backend", "file"),
 32        )
 33
 34        self.management_backend = None
 35        for backend in _FILE_BACKENDS:
 36            if issubclass(backend, ManageableBackend) and backend.name == management_backend_name:
 37                self.management_backend = backend(usage)
 38        if self.management_backend is None:
 39            LOGGER.warning(
 40                f"Storage backend configuration for {usage.value} is "
 41                f"invalid: {management_backend_name}"
 42            )
 43
 44        self.backends = []
 45        for backend in _FILE_BACKENDS:
 46            if usage not in backend.allowed_usages:
 47                continue
 48            if isinstance(self.management_backend, backend):
 49                self.backends.append(self.management_backend)
 50            elif not issubclass(backend, ManageableBackend):
 51                self.backends.append(backend(usage))
 52
 53    @property
 54    def manageable(self) -> bool:
 55        """
 56        Whether this file manager is able to manage files.
 57        """
 58        return self.management_backend is not None and self.management_backend.manageable
 59
 60    def list_files(self, manageable_only: bool = False) -> Generator[str]:
 61        """
 62        List available files.
 63        """
 64        for backend in self.backends:
 65            if manageable_only and not isinstance(backend, ManageableBackend):
 66                continue
 67            yield from backend.list_files()
 68
 69    def file_url(
 70        self,
 71        name: str | None,
 72        request: HttpRequest | Request | None = None,
 73        use_cache: bool = True,
 74    ) -> str:
 75        """
 76        Get URL for accessing the file.
 77        """
 78        if not name:
 79            return ""
 80
 81        if isinstance(request, Request):
 82            request = request._request
 83
 84        for backend in self.backends:
 85            if backend.supports_file(name):
 86                return backend.file_url(name, request)
 87
 88        LOGGER.warning(f"Could not find file backend for file: {name}")
 89        return ""
 90
 91    def themed_urls(
 92        self,
 93        name: str | None,
 94        request: HttpRequest | Request | None = None,
 95    ) -> dict[str, str] | None:
 96        """
 97        Get URLs for each theme variant when filename contains %(theme)s.
 98
 99        Returns dict mapping theme to URL if %(theme)s present, None otherwise.
100        """
101        if not name:
102            return None
103
104        if isinstance(request, Request):
105            request = request._request
106
107        for backend in self.backends:
108            if backend.supports_file(name):
109                return backend.themed_urls(name, request)
110
111        return None
112
113    def _check_manageable(self) -> None:
114        if not self.manageable:
115            raise ImproperlyConfigured("No file management backend configured.")
116
117    def save_file(self, file_path: str, content: bytes) -> None:
118        """
119        Save file contents to storage.
120        """
121        self._check_manageable()
122        assert self.management_backend is not None  # nosec
123        return self.management_backend.save_file(file_path, content)
124
125    def save_file_stream(self, file_path: str) -> Iterator:
126        """
127        Context manager for streaming file writes.
128
129        Args:
130            file_path: Relative file path
131
132        Returns:
133            Context manager that yields a writable file-like object
134
135        Usage:
136            with manager.save_file_stream("output.csv") as f:
137                f.write(b"data...")
138        """
139        self._check_manageable()
140        assert self.management_backend is not None  # nosec
141        return self.management_backend.save_file_stream(file_path)
142
143    def delete_file(self, file_path: str) -> None:
144        """
145        Delete file from storage.
146        """
147        self._check_manageable()
148        assert self.management_backend is not None  # nosec
149        return self.management_backend.delete_file(file_path)
150
151    def file_exists(self, file_path: str) -> bool:
152        """
153        Check if a file exists.
154        """
155        self._check_manageable()
156        assert self.management_backend is not None  # nosec
157        return self.management_backend.file_exists(file_path)
158
159
160MANAGERS = {usage: FileManager(usage) for usage in list(FileUsage)}
161
162
163def get_file_manager(usage: FileUsage) -> FileManager:
164    return MANAGERS[usage]
LOGGER = <BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=())>
class FileManager:
 28class FileManager:
 29    def __init__(self, usage: FileUsage) -> None:
 30        management_backend_name = CONFIG.get(
 31            f"storage.{usage.value}.backend",
 32            CONFIG.get("storage.backend", "file"),
 33        )
 34
 35        self.management_backend = None
 36        for backend in _FILE_BACKENDS:
 37            if issubclass(backend, ManageableBackend) and backend.name == management_backend_name:
 38                self.management_backend = backend(usage)
 39        if self.management_backend is None:
 40            LOGGER.warning(
 41                f"Storage backend configuration for {usage.value} is "
 42                f"invalid: {management_backend_name}"
 43            )
 44
 45        self.backends = []
 46        for backend in _FILE_BACKENDS:
 47            if usage not in backend.allowed_usages:
 48                continue
 49            if isinstance(self.management_backend, backend):
 50                self.backends.append(self.management_backend)
 51            elif not issubclass(backend, ManageableBackend):
 52                self.backends.append(backend(usage))
 53
 54    @property
 55    def manageable(self) -> bool:
 56        """
 57        Whether this file manager is able to manage files.
 58        """
 59        return self.management_backend is not None and self.management_backend.manageable
 60
 61    def list_files(self, manageable_only: bool = False) -> Generator[str]:
 62        """
 63        List available files.
 64        """
 65        for backend in self.backends:
 66            if manageable_only and not isinstance(backend, ManageableBackend):
 67                continue
 68            yield from backend.list_files()
 69
 70    def file_url(
 71        self,
 72        name: str | None,
 73        request: HttpRequest | Request | None = None,
 74        use_cache: bool = True,
 75    ) -> str:
 76        """
 77        Get URL for accessing the file.
 78        """
 79        if not name:
 80            return ""
 81
 82        if isinstance(request, Request):
 83            request = request._request
 84
 85        for backend in self.backends:
 86            if backend.supports_file(name):
 87                return backend.file_url(name, request)
 88
 89        LOGGER.warning(f"Could not find file backend for file: {name}")
 90        return ""
 91
 92    def themed_urls(
 93        self,
 94        name: str | None,
 95        request: HttpRequest | Request | None = None,
 96    ) -> dict[str, str] | None:
 97        """
 98        Get URLs for each theme variant when filename contains %(theme)s.
 99
100        Returns dict mapping theme to URL if %(theme)s present, None otherwise.
101        """
102        if not name:
103            return None
104
105        if isinstance(request, Request):
106            request = request._request
107
108        for backend in self.backends:
109            if backend.supports_file(name):
110                return backend.themed_urls(name, request)
111
112        return None
113
114    def _check_manageable(self) -> None:
115        if not self.manageable:
116            raise ImproperlyConfigured("No file management backend configured.")
117
118    def save_file(self, file_path: str, content: bytes) -> None:
119        """
120        Save file contents to storage.
121        """
122        self._check_manageable()
123        assert self.management_backend is not None  # nosec
124        return self.management_backend.save_file(file_path, content)
125
126    def save_file_stream(self, file_path: str) -> Iterator:
127        """
128        Context manager for streaming file writes.
129
130        Args:
131            file_path: Relative file path
132
133        Returns:
134            Context manager that yields a writable file-like object
135
136        Usage:
137            with manager.save_file_stream("output.csv") as f:
138                f.write(b"data...")
139        """
140        self._check_manageable()
141        assert self.management_backend is not None  # nosec
142        return self.management_backend.save_file_stream(file_path)
143
144    def delete_file(self, file_path: str) -> None:
145        """
146        Delete file from storage.
147        """
148        self._check_manageable()
149        assert self.management_backend is not None  # nosec
150        return self.management_backend.delete_file(file_path)
151
152    def file_exists(self, file_path: str) -> bool:
153        """
154        Check if a file exists.
155        """
156        self._check_manageable()
157        assert self.management_backend is not None  # nosec
158        return self.management_backend.file_exists(file_path)
FileManager(usage: authentik.admin.files.usage.FileUsage)
29    def __init__(self, usage: FileUsage) -> None:
30        management_backend_name = CONFIG.get(
31            f"storage.{usage.value}.backend",
32            CONFIG.get("storage.backend", "file"),
33        )
34
35        self.management_backend = None
36        for backend in _FILE_BACKENDS:
37            if issubclass(backend, ManageableBackend) and backend.name == management_backend_name:
38                self.management_backend = backend(usage)
39        if self.management_backend is None:
40            LOGGER.warning(
41                f"Storage backend configuration for {usage.value} is "
42                f"invalid: {management_backend_name}"
43            )
44
45        self.backends = []
46        for backend in _FILE_BACKENDS:
47            if usage not in backend.allowed_usages:
48                continue
49            if isinstance(self.management_backend, backend):
50                self.backends.append(self.management_backend)
51            elif not issubclass(backend, ManageableBackend):
52                self.backends.append(backend(usage))
management_backend
backends
manageable: bool
54    @property
55    def manageable(self) -> bool:
56        """
57        Whether this file manager is able to manage files.
58        """
59        return self.management_backend is not None and self.management_backend.manageable

Whether this file manager is able to manage files.

def list_files(self, manageable_only: bool = False) -> Generator[str]:
61    def list_files(self, manageable_only: bool = False) -> Generator[str]:
62        """
63        List available files.
64        """
65        for backend in self.backends:
66            if manageable_only and not isinstance(backend, ManageableBackend):
67                continue
68            yield from backend.list_files()

List available files.

def file_url( self, name: str | None, request: django.http.request.HttpRequest | rest_framework.request.Request | None = None, use_cache: bool = True) -> str:
70    def file_url(
71        self,
72        name: str | None,
73        request: HttpRequest | Request | None = None,
74        use_cache: bool = True,
75    ) -> str:
76        """
77        Get URL for accessing the file.
78        """
79        if not name:
80            return ""
81
82        if isinstance(request, Request):
83            request = request._request
84
85        for backend in self.backends:
86            if backend.supports_file(name):
87                return backend.file_url(name, request)
88
89        LOGGER.warning(f"Could not find file backend for file: {name}")
90        return ""

Get URL for accessing the file.

def themed_urls( self, name: str | None, request: django.http.request.HttpRequest | rest_framework.request.Request | None = None) -> dict[str, str] | None:
 92    def themed_urls(
 93        self,
 94        name: str | None,
 95        request: HttpRequest | Request | None = None,
 96    ) -> dict[str, str] | None:
 97        """
 98        Get URLs for each theme variant when filename contains %(theme)s.
 99
100        Returns dict mapping theme to URL if %(theme)s present, None otherwise.
101        """
102        if not name:
103            return None
104
105        if isinstance(request, Request):
106            request = request._request
107
108        for backend in self.backends:
109            if backend.supports_file(name):
110                return backend.themed_urls(name, request)
111
112        return None

Get URLs for each theme variant when filename contains %(theme)s.

Returns dict mapping theme to URL if %(theme)s present, None otherwise.

def save_file(self, file_path: str, content: bytes) -> None:
118    def save_file(self, file_path: str, content: bytes) -> None:
119        """
120        Save file contents to storage.
121        """
122        self._check_manageable()
123        assert self.management_backend is not None  # nosec
124        return self.management_backend.save_file(file_path, content)

Save file contents to storage.

def save_file_stream(self, file_path: str) -> Iterator:
126    def save_file_stream(self, file_path: str) -> Iterator:
127        """
128        Context manager for streaming file writes.
129
130        Args:
131            file_path: Relative file path
132
133        Returns:
134            Context manager that yields a writable file-like object
135
136        Usage:
137            with manager.save_file_stream("output.csv") as f:
138                f.write(b"data...")
139        """
140        self._check_manageable()
141        assert self.management_backend is not None  # nosec
142        return self.management_backend.save_file_stream(file_path)

Context manager for streaming file writes.

Args: file_path: Relative file path

Returns: Context manager that yields a writable file-like object

Usage: with manager.save_file_stream("output.csv") as f: f.write(b"data...")

def delete_file(self, file_path: str) -> None:
144    def delete_file(self, file_path: str) -> None:
145        """
146        Delete file from storage.
147        """
148        self._check_manageable()
149        assert self.management_backend is not None  # nosec
150        return self.management_backend.delete_file(file_path)

Delete file from storage.

def file_exists(self, file_path: str) -> bool:
152    def file_exists(self, file_path: str) -> bool:
153        """
154        Check if a file exists.
155        """
156        self._check_manageable()
157        assert self.management_backend is not None  # nosec
158        return self.management_backend.file_exists(file_path)

Check if a file exists.

MANAGERS = {<FileUsage.MEDIA: 'media'>: <FileManager object>, <FileUsage.REPORTS: 'reports'>: <FileManager object>}
def get_file_manager( usage: authentik.admin.files.usage.FileUsage) -> FileManager:
164def get_file_manager(usage: FileUsage) -> FileManager:
165    return MANAGERS[usage]