Media Access Control
Every FileField and ImageField in Waldur is stored by DatabaseStorage, the
global default storage. Uploads do not go to disk or S3 — they become rows in
waldur_core.media.models.File, and they are all served by a single endpoint:
1 | |
That endpoint has permission_classes = (). The only thing it knows about a
file is its storage path, so access rules are keyed by the upload_to
prefix, and they are deny by default: a file whose prefix has no
registered rule is served to nobody, staff included.
Adding a file field
Whenever you add a FileField or ImageField, you must also declare who may
download it. Skipping this step is not silently insecure — it is silently
broken, and CoverageTest in waldur_core/media/tests/test_access_registry.py
fails until you do.
Declare rules in a media_access.py module at the root of your app. It is
imported automatically at startup; no registration in apps.py is needed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
That is the real rule for payment proofs, verbatim.
Choosing a rule
| Helper | Who gets the file |
|---|---|
register_public(prefix) |
Everyone, including anonymous users |
register_authenticated(prefix) |
Any logged-in user, no per-object check |
register_staff_only(prefix) |
Staff and support only |
register(prefix, check) |
Custom check(file, user) -> bool |
queryset_rule(...) |
Whoever the app's own read rule allows |
Prefer queryset_rule. It looks the file up in its owning table and hands the
result to the same filter the ViewSet uses — usually
filter_queryset_for_user or a manager's filter_for_user — so a download
cannot outlive the permission the API itself enforces.
Use register_public only for files already reachable through an anonymous
API endpoint, and say which one in a comment. Everything on the unauthenticated
marketplace catalogue and the public call-for-proposals page falls in this
category.
Deriving the prefix
Never write the prefix as a literal. Derive it, so renaming an upload_to
cannot leave a stale rule registered and the new path wide open:
access.upload_prefix(Model, "field")— for a stringupload_to. A strftime-formatted value such asmatrix_exports/%Y/%m/is truncated at the first format token.access.image_prefix(Model)— for a field usingget_upload_pathfromwaldur_core.media.mixins, including anything usingImageModelMixin.
get_upload_path builds paths from the bare model name with no app label, so
two image-bearing models with the same class name in different apps would share
a prefix. test_model_names_used_as_prefixes_are_unique guards against that.
Constraints on media_access modules
MediaConfig.ready() runs before constance and before the plugin apps' own
ready(). A media_access module therefore must not:
- touch the database or
constance.configat import time; - import its own app's
viewsorserializers.
Building a closure is fine; evaluating one is not. If you need a queryset
helper that currently lives in views.py, move it to managers.py — see
matrix_chat.managers.get_accessible_room_ids.
A media_access module that fails to import raises rather than warning. Under
default-deny a swallowed import error would silently stop serving every file
that app owns.
Out-of-tree plugins
Waldur loads any plugin registered under the waldur_extensions entry point,
including packages outside this repository. CoverageTest only walks the
models of installed apps in CI, so it cannot warn a third-party plugin author.
If you maintain a Waldur plugin with a FileField and it is not in this
repository, add a media_access.py to it. Without one, its files return 404
for every user after upgrading. The failure is fail-closed rather than a data
leak, but it is still an outage for that plugin's downloads.
Testing
For a test that needs a synthetic registry, use the context manager — never clear the registry directly. Autodiscovery runs once per process, so an unrestored registry would leave every later test in the same worker denying everything:
1 2 3 | |
Reference
src/waldur_core/media/access.py— the registrysrc/waldur_core/media/apps.py— autodiscoverysrc/waldur_core/media/tests/test_access_registry.py— registry and coverage testssrc/waldur_mastermind/marketplace/media_access.py— a worked example covering public, authenticated and scope-checked prefixes