Commit Graph
1237 Commits
Author SHA1 Message Date
Nathan Vasse e50ace270e (backend) add a storage gauge information link setting
Operators may document how storage quotas work for their users. The
optional FRONTEND_STORAGE_GAUGE_INFORMATION_LINK setting is exposed
through the config API so the frontend gauge can link to that page.
2026-07-23 17:08:11 +02:00
Nathan Vasse a84851efff (backend) gate move-to-root and duplicate on the upload entitlement
Both actions grow the acting user's storage usage without going
through an upload: duplicating makes them creator of a new sized
copy, and moving a file to the root without a direct access
reassigns its creator. An over-quota user could use them to keep
taking ownership of storage. Moving also invalidates the previous
creator's usage cache since the reassignment only triggers the
post_save invalidation for the new creator.
2026-07-23 17:08:10 +02:00
Nathan Vasse 0f7ee3a3ed (backend) add a local entitlements backend with storage limits
Deployments without a DeployCenter service had no way to enforce
storage quotas. This backend computes each user's usage locally and
applies a configurable default limit, overridable per user in the
admin (0 meaning unlimited) and skippable for users created before a
cutoff so quotas can be rolled out to new users only. The quota is
soft: can_upload runs before the file size is known, so one upload
can overshoot before the next one is blocked.
2026-07-23 17:08:10 +02:00
Nathan Vasse 6c61275669 🐛(backend) exclude hard-deleted items from storage computation
Hard-deleted items no longer occupy storage but still counted in the
creator's usage, inflating the quota gauge and blocking uploads for
users who had cleaned up their trash. The new partial covering index
keeps the per-creator sum an index-only scan now that the quota check
runs it on every uncached upload.
2026-07-23 17:08:10 +02:00
Nathan Vasse 780c63bb08 (backend) invalidate per-user storage caches on item writes
The quota gauge reads the storage usage from cache (directly for the
local backend, via DeployCenter's metrics for the remote one). Both
caches must be dropped as soon as a write changes a user's usage,
otherwise the gauge shows stale numbers until the timeout. A post_save
signal covers item saves, and the code paths doing bulk updates that
bypass signals (signup invitations, user reconciliation, hard delete
of a tree) invalidate explicitly for every impacted creator.
2026-07-23 17:08:10 +02:00
Nathan Vasse d9cfb4dae1 (backend) expose the user quota in the entitlements API
The frontend needs the storage usage and limit to render a quota
gauge. Entitlements backends can now return a quota (usage/limit,
locked when the organization quota is reached, or an explicit error
state) and the DeployCenter backend derives the can_upload reason
from the resolve level when the service does not provide one, so
the gauge and the upload errors stay consistent.
2026-07-23 17:08:10 +02:00
Nathan Vasse 99d3a4d20b (backend) expose the can_upload reason as the API error code
The upload gates returned a generic "permission_denied" code whatever
the entitlements backend's refusal reason. Forwarding the backend's
reason as the DRF error code lets the frontend map each rejection to
a specific, translatable message instead of a catch-all one.
2026-07-23 17:08:10 +02:00
Nicolas Clerc 5203cc4483 (backend) show human readable item size in the admin
The item size was displayed as a raw byte count in the admin detail
page, which is hard to read. Format it with filesizeformat.
2026-07-23 09:54:18 +02:00
Nicolas Clerc 5cd2816435 (backend) expose item existence in the malware detection admin
Detection records whose item is gone can only be cleaned up manually.
Add an "item exists" column and filter to the admin so orphan records
can be spotted and deleted easily.
2026-07-23 09:54:18 +02:00
Nicolas Clerc e5f22ea2c8 🐛(backend) delete malware detection record when purging an item
When an item was purged while its analysis was still pending or
processing, the detection record was left behind: the analysis kept
being relaunched and crashed on the missing file, leaving the record
stuck forever and holding a processing slot.
2026-07-23 09:54:05 +02:00
Nicolas Clerc 722823cbad ♻️(settings) replace empty upload ACL with a "default" sentinel
An empty AWS_S3_UPLOAD_ACL in an env file is easy to mistake for
an unset variable, and unset must keep meaning "private". The
explicit "default" value states the intent: let the bucket's
default object ACL apply. The frontend translates it where the
config is read, so the drivers keep sending exactly the ACL they
are given.
2026-07-22 17:10:22 +02:00
Nicolas Clerc 3e9a0dd883 (frontend) send the upload ACL from the backend config
The upload ACL is now configurable and may be empty for storages that
do not support ACLs, so the PUT request must send exactly the ACL
signed in the upload policy. Read the value from the config endpoint
and pass it down to the S3 upload request.
2026-07-22 16:02:04 +02:00
Nicolas Clerc 8ba9d7b3f9 (backend) make the upload ACL configurable
GCS based storages like S3NS reject presigned PUT URLs signing the
x-amz-acl header: their S3 compatibility layer consumes x-amz-*
headers having an x-goog-* equivalent, so the signature check no
longer finds the header in the request. Objects are private by
default on S3 and GCS, so the ACL can simply be dropped by setting
AWS_S3_UPLOAD_ACL to an empty string. The setting is exposed in the
config endpoint so the frontend can send a header matching the
signature.

See https://documentation.s3ns.fr/storage/docs/aws-simple-migration
2026-07-22 16:02:04 +02:00
Nicolas Clerc d2c1e47b5d ⬆️(dependencies) update pillow
Raise the transitive pillow constraint to 12.3.0 to fix
CVE-2026-54058 (GHSA-pg7v-jwj7-p798), flagged by the trivy
scan on the backend image.
2026-07-22 15:45:12 +02:00
Nicolas Clerc ab2855ac0b ⬆️(dependencies) update django
Bump django to 5.2.15 to fix GHSA-mm6v-q8q9-pgcf.
2026-07-22 15:45:12 +02:00
Nicolas Clerc 20e5d6fec9 ⬆️(dependencies) update idna
Bump the transitive idna to 3.18 to fix GHSA-65pc-fj4g-8rjx, where
crafted input to idna.encode() bypasses the CVE-2024-3651 mitigation.
2026-07-22 15:45:12 +02:00
Nicolas Clerc 75dcb214eb ⬆️(dependencies) update python base image
Rebase the backend image on python:3.13.14-alpine (Alpine 3.24) to
pull the patched openssl fixing CVE-2026-31789. Stays within the
pinned 3.13 runtime, so no dependency re-resolution is needed.
2026-07-22 15:45:12 +02:00
Nicolas Clerc 756203e708 ⬆️(dependencies) update next, vite and turbo
Next 15.5.18, Vite 6.4.3 and Turbo 2.9.14 patch several advisories:
Next.js server components denial of service (GHSA-26hh-7cqf-hhc6),
Vite server.fs.deny bypass (GHSA-fx2h-pf6j-xcff) and Turbo local
code execution on untrusted repositories (GHSA-3qcw-2rhx-2726).
2026-07-22 15:45:12 +02:00
Nicolas Clerc 6ff79c6fca 🔧(docker) harden the Dockerfile
Address the maintainability findings raised by the new SonarCloud
docker analyzer: drop the floating pip upgrade (nothing uses pip in
the image, the build relies on uv, and the base image already ships
a current pip), stop storing apk indexes in the image layers, quote
DRIVE_STATIC_ROOT and sort the core package list.
2026-07-22 15:45:12 +02:00
Nicolas Clerc bd6f6b8039 🐛(docker) pin collabora image and adapt to its new runtime contract
The collabora/code image published on 2026-07-18 (26.04.2.2.1) drops
the shell and the start script: our CMD-SHELL healthcheck could not
run at all (no /bin/sh) and extra_params was no longer read, so SSL
came back on and broke the plain-http WOPI discovery. Every e2e job
failed with "container drive-collabora-1 is unhealthy".

Pin the image and migrate to the new contract: coolwsd options go
through command arguments, and the health probe relies on the
built-in coolwsd --probe, overridden to target the http endpoint
since it defaults to https.
2026-07-22 15:45:12 +02:00
Nicolas Clerc 1776a7187a 🔖(minor) bump release version 0.20.0
Added

- (backend) allow converting a file while it is being analyzed
- (frontend) add file type, contact and modification date topbar filters
- (frontend) add location, file type, contact and date search filters

Fixed

- 🐛(nginx) let search engines see the noindex directive to drop public URLs
- 🐛(backend) find deleted root items when searching the trashbin
- 🐛(backend) exclude folders from file type search results
- 🐛(frontend) keep uploaded items usable while malware analysis runs
- 🐛(backend) stream export files from S3 without buffering
v0.20.0 v0.20.0-preprod
2026-07-15 13:44:19 +02:00
Nicolas Clerc 0d40256363 🐛(nginx) let search engines see the noindex directive
robots.txt blocked all crawling, so crawlers could never fetch the
pages and never saw the noindex meta tag: URLs discovered through
external links could still end up listed in search results.

Allow crawling in robots.txt and add a X-Robots-Tag noindex header on
frontend pages and media files so every response carries the directive.
2026-07-08 17:45:54 +02:00
Nicolas Clerc 8e5fb9971e 🐛(backend) find deleted root items when searching the trashbin
Search rebuilds the visible set from the user's accesses, skipping those
whose item was soft deleted. A root item holds its own access, so
deleting it made it unreachable from any location, trashbin included.
Hard deleted items remain excluded, and the scope filter still keeps
deleted items out of regular search results.
2026-07-08 16:37:59 +02:00
Nicolas Clerc ca6bbd50f7 🐛(backend) exclude folders from file type search results
The category filter keeps folders on explorer listings so navigation
stays possible. A search is not a navigation: folders have no file type
and only add noise to the results.
2026-07-08 16:31:11 +02:00
Nathan Vasse 4b9eaa4977 ⬆️(frontend) refresh transitive pins in the lockfile
A reinstall picked up new radix-ui and typescript-eslint patch
releases within the ranges already declared, so the lockfile is
updated to keep local installs and CI resolving the same versions.
2026-07-08 14:15:03 +02:00
Nathan Vasse 78501a2881 ♻️(frontend) drop a leftover spread in the category filter options
The reset entry moved out of the options list when the filters
switched to the ui-kit built-in reset, leaving a pointless wrapping
spread and a comment describing an option that no longer exists.
2026-07-08 14:15:03 +02:00
Nathan Vasse 99ce2ebb26 🐛(frontend) clear the modified filter when its option is deselected
The ui-kit Filter emits an empty key when the active option is
deselected. Wrapping it in a structured value anyway made the filter
look active while carrying no usable bounds, so we now propagate
undefined to actually clear it.
2026-07-08 14:15:02 +02:00
Nathan Vasse cdc71c956d (frontend) adapt link reach check to new dropdown markup
The updated ui-kit renders the selected menu item indicator as an
SVG with the c__dropdown-menu-item__check class instead of the old
material-icons checked span, so the share e2e tests could no longer
find the icon and failed in CI.
2026-07-08 14:15:02 +02:00
Nathan Vasse 1241db9d1f 💄(frontend) update search modal spaces
Align the paddings with the one on the sketches.
2026-07-08 14:15:02 +02:00
Nathan Vasse 97bdff4b75 ⬆️(frontend) bump ui-kit version
We need this new version that implement all the required changes
for the filters design review.
2026-07-08 14:14:34 +02:00
Nathan Vasse b4c48a7f81 💄(frontend) let the filters bar scroll on small screens
On mobile the filter buttons overflowed the viewport with no way to
reach the last ones. The bar is now wrapped in a SmartScroller, like
in the search modal, and the mobile background moves to the container
so the surface color covers the whole scrollable area. The explorer
container also takes the full width under the tablet breakpoint.
2026-07-08 11:45:36 +02:00
Nathan Vasse f068dd079b ♻️(frontend) keep the modified filter as a structured value
Storing only the computed date bounds in the filters lost the selected
preset, so the filter could not restore its own state and the custom
range needed extra local state and top-down resets. The filters now
carry a frontend-only "modified" value (preset key or custom range)
and the drivers convert it to updated_at_* query params right before
each request. The custom calendar is hidden on mobile where it does
not have enough room to render comfortably.
2026-07-08 11:45:36 +02:00
Nathan Vasse f6142372a5 ♻️(frontend) use UserSearchFilter for the contact filter
The ui-kit now provides a dedicated user search filter with the reset
button, the empty state and the selected row rendering built in. The
bespoke SearchFilter wiring (manual reset entry, custom item renderer)
is no longer needed.
2026-07-08 11:45:36 +02:00
Nathan Vasse a74053db0a 💄(frontend) rely on the ui-kit built-in filter reset
The Filter component now ships its own reset button, so the custom
reset option at the top of the type and location lists duplicated it.
Icons are also aligned with the design review: mini file icons and
small location icons.
2026-07-08 11:45:36 +02:00
Nathan Vasse 625c5455bd (e2e) follow the user menu button rename
The ui-kit renamed the accessible name of the user menu trigger from
"Open user menu" to "User menu". The tests use this button to detect
that the layout is fully loaded, so the selectors must follow.
2026-07-08 11:45:35 +02:00
Nathan Vasse ab45c32aec (e2e) refresh the wopi after paste canvas baseline
Same cause as the empty doc baseline: the collabora update renders the
canvas 951px wide so the April screenshot fails on the size check. The
new baseline is the image rendered by the CI runner, taken from the
attempt matching the majority of retries (one retry showed a transient
scroll offset that the screenshot polling absorbs at comparison time).
2026-07-08 11:24:42 +02:00
Nathan Vasse 1301c6a8c0 (e2e) retry clearing the silent login retry key
Clearing the django session cookie can trigger a navigation while the
localStorage evaluation runs, which destroys the execution context and
fails the test on webkit. Retrying until the page settles removes the
race.
2026-07-08 11:24:42 +02:00
Nathan Vasse f3b12b5eb0 (e2e) refresh the wopi empty doc canvas baseline
The CI pulls collabora/code:latest and a recent release changed the
canvas rendering: it is now 8px narrower and loses the page border, so
the screenshot comparison fails on the size check before even diffing.
The new baseline is the image produced by the CI runner itself.
2026-07-08 11:24:42 +02:00
Nathan Vasse 44c73de815 (e2e) assert the dropdown check icon via material-icons
The published ui-kit 0.24.0 renders the selected dropdown indicator as
a material icon span (.material-icons.checked). The class used before,
c__dropdown-menu-item__check, only exists in a not yet released ui-kit
version, so the locator matched nothing on the CI.
2026-07-08 11:24:42 +02:00
Nathan Vasse c9f86d91af (e2e) repair the wopi conversion modal tests
These tests never passed since their introduction. The modal title is
not rendered as a heading, so we target the dialog role instead. The
placeholder-injecting routes shadowed the convert ability mock and,
through route.continue(), sent the convert POST to the real backend
which denied it: they now re-inject the ability and use
route.fallback() so earlier mocks still apply. The confirming test
also mocks the placeholder GET, otherwise the poller sees a real 404
and removes the placeholder as a failed conversion.
2026-07-08 11:24:42 +02:00
Nathan Vasse 524bac7bf7 (e2e) assert the dropdown check icon by its class
Since ui-kit 0.24 the selected item indicator in dropdown menus is an
SVG instead of a material icon, so the item text no longer contains
"check" and the share link reach tests fail.
2026-07-08 11:24:42 +02:00
Nicolas Clerc 25e693b1d8 🔒️(backend) constrain joserfc >=1.6.8 for CVE-2026-49852
joserfc <1.6.8 accepts an empty HMAC key when verifying a JWT,
allowing token forgery if the secret is misconfigured (empty).
The dependency is pulled transitively via django-lasuite, which
does not enforce a minimum bound.
2026-07-08 09:31:31 +02:00
Nathan Vasse 5d969b8d74 (e2e) cover the help menu rendering
We verify the help menu shows the configured options, drops the contact
entry when no support email is set, and stays hidden when the config is
empty. The e2e env gets a help menu config so the menu is reachable.
2026-06-29 18:05:56 +02:00
Nathan Vasse 5c64f2beb6 (frontend) add a help menu in the left panel footer
When the backend provides FRONTEND_HELP_MENU_CONFIG, we render the
ui-kit HelpMenu in the left panel footer so users can reach the
documentation, legal pages and support. The menu is hidden when no
config is set to avoid an empty footer. We also hide the unused
cunningham toast container which would otherwise capture clicks on the
footer.
2026-06-29 18:05:48 +02:00
Nathan Vasse 99bcf05be9 (backend) expose a help menu config to the frontend
The frontend needs a configurable help menu pointing to documentation,
legal pages and a support contact. We expose FRONTEND_HELP_MENU_CONFIG
through the config endpoint so deployments can tune these links without
a frontend rebuild.
2026-06-29 18:05:43 +02:00
Nathan VasseandNicolas Clerc 330a782aab 🐛(frontend) stabilize the custom modification date picker
Committing the picked range on the calendar's onChange called setRange,
which rebuilt the Filter options memo and remounted the calendar. That
detached its OK button mid-interaction, so clicking OK raced against the
remount and timed out on firefox and webkit. We now stash the range in a
ref while picking and only commit it on OK, when the panel closes anyway.
2026-06-29 16:49:52 +02:00
Nathan VasseandNicolas Clerc d0d3083c3d 🐛(frontend) let jest resolve ui-kit exports subpaths
The icons subpath import only exists in the ui-kit package.json
"exports" map. Jest's ts-jest transform forced classic "node"
module resolution, which ignores "exports" and failed to find
"@gouvfr-lasuite/ui-kit/icons". Switching to "bundler" matches
the app tsconfig so the unit tests compile again.
2026-06-29 16:49:52 +02:00
Nathan VasseandNicolas Clerc d1f6fc95f6 (e2e) cover the custom modification date range
The new test picks a custom range through the calendar, asserts the
items are filtered to the selected window, checks the option label
reflects the bounds, and that resetting falls back to the default label.
2026-06-29 16:49:52 +02:00
Nathan VasseandNicolas Clerc 12aceaf73e ♻️(frontend) import icons from the ui-kit icons subpath
The icons now live under the dedicated `@gouvfr-lasuite/ui-kit/icons`
entry point. Importing them from there keeps the icon imports aligned
with the package layout.
2026-06-29 16:49:52 +02:00
Nathan VasseandNicolas Clerc ec51aca603 (frontend) make the search modal filters scroll
The filter row is wrapped in a SmartScroller so it overflows gracefully
when the filters do not fit. The global reset button is dropped since
each filter already exposes its own reset, making it redundant.
2026-06-29 16:49:52 +02:00