1282 Commits
Author SHA1 Message Date
Nicolas Clerc 2117f0dbf1 🐛(ci) run crowdin workflows on node 22
The frontend package.json now requires node >= 22, so the crowdin
workflows fail at yarn install with the node 20 they still request.
Align them with drive-frontend.yml and update the reusable job
default so future callers cannot regress silently.
v0.21.0
2026-08-07 15:30:19 +02:00
Nicolas Clerc 464cd7ab2f 🔖(minor) bump release version 0.21.0
Added

- (backend) make the upload ACL configurable to support GCS based storages
- (frontend) show the messages widget button on the homepage
- (frontend) open the messages widget from the help menu
- (backend) add an item batch share endpoint gated by ALLOW_SHARE_IMPORT_FILE
- (frontend) share an item with contacts imported from a file
- (backend) add a quota_excluded flag on items
- (backend) apply per-audience attributes to external api items
- (backend) add a grant_unlimited_storage command

Changed

- 🔧(docker) drop the unused pip upgrade and apk caches from the image
- (backend) expose item existence in the malware detection admin
- (backend) show human readable item size in the admin
- 🚚(global) move favorite items API endpoint to `/items/favorites/`

Fixed

- 🐛(docker) pin collabora image and adapt to its new runtime contract
- 🐛(backend) delete malware detection record when purging an item
- 🔒️(backend) reject unsafe filenames requested by WOPI renames
- 🔒️(backend) analyze file content written through WOPI
v0.21.0-preprod
2026-08-07 15:30:19 +02:00
Nicolas Clerc 9ec728847d 🔒️(backend) scan files written through WOPI
PutFile wrote the content straight to object storage, while malware
detection only ran on the upload endpoint. Editing a document was
therefore a way to store content that was never analysed.

The item stays READY during the analysis: a collaborator cannot open a
file that is not READY, so flipping the state would eject everyone from
the document on every save.
2026-08-07 12:39:16 +02:00
Nicolas Clerc 5a4b36c942 🔒️(backend) reject unsafe WOPI rename targets
The requested name was decoded from UTF-7 and used as is, so a path
separator could reach the storage key and leave the item without an
extension. A second rename then granted it any extension, bypassing the
allowed list that only the upload path enforced.

Extensions are now compared case insensitively on both sides, so entries
declared with capitals are no longer dead.
2026-08-07 12:39:16 +02:00
Nicolas Clerc 2a5bbfcfea ⬆️(mail) bump @html-to/text-cli to 0.6.0
Close the Snyk upgrade PR #722 and refresh the html-to-text chain
used to derive the plain text mail templates.
2026-08-06 16:19:15 +02:00
Nicolas Clerc 60f7e3760c 🔒️(backend) resolve template files through a static allowlist
Snyk Code flags the f-string path built from the request extension as
a path traversal. The DRF ChoiceField already restricts the value but
static analysis cannot follow it. Selecting the file name from a
static mapping makes the sink independent from user input, and the
serializer choices now derive from that single mapping.
2026-08-06 16:19:15 +02:00
Nicolas Clerc 5947ecdc95 ⬆️(docker) bump the frontend image to alpine 3.24
The alpine 3.22 base of nginx-unprivileged carries 69 known CVEs
including a critical one in openssl (CVE-2026-31789). Renovate does
not manage Dockerfile images on this repo, so the bump is manual.
2026-08-06 16:19:15 +02:00
Nicolas Clerc 867527042d ⬆️(dependencies) bump nanoid to 3.3.17
Fix CVE-2026-67213 (infinite loop in customRandom), a transitive
dependency flagged by the Snyk check on both yarn lockfiles.
2026-08-06 16:19:15 +02:00
Nicolas Clerc f2fc2acf65 ⬆️(ci) bump the build-mails job to node 22
mjml v5 pulls commander@15 which requires node >= 22.12. Include the
yarn.lock and build scripts in the mail templates cache key so
dependency bumps regenerate the templates.
2026-08-06 16:19:15 +02:00
Nicolas Clerc 6827ff633a 🐛(mail) allow mjml includes when building templates
mjml v5 ignores mj-include by default, silently dropping the whole
mail head (Django load tag, title, fonts and styles) from the
generated templates. Opt back in.
2026-08-06 14:17:17 +02:00
Nicolas Clerc d22851554b ⬆️(docker) bump mail-builder to node 22
mjml v5 pulls commander@15 which requires node >= 22.12, the
backend image mail stage was still building with node 20.
2026-08-06 14:17:17 +02:00
renovate[bot]andNicolas Clerc 9ca079670c ⬆️(dependencies) update mjml to v5 [SECURITY]
Fix the vulnerabilities of the mjml v4 dependency chain (ReDoS in
minimatch and html-minifier, brace-expansion DoS among others).
Cherry-picked from renovate PR #754.
2026-08-06 14:17:17 +02:00
renovate[bot]andNicolas Clerc 82848fa403 ⬆️(dependencies) update next to v15.5.21 [SECURITY]
Fix multiple Snyk findings on next 15.5.18, including two SSRF
(CVE-2026-64649, CVE-2026-64645). Cherry-picked from renovate PR #705.
2026-08-06 14:17:17 +02:00
Nicolas Clerc b761d1dc7d ⬆️(backend) bump cryptography to 50.0.0
Fix CVE-2026-69247 (High) reported by trivy. cryptography is a
transitive dependency pinned in uv.lock, no direct requirement
to update.
2026-08-06 10:02:11 +02:00
Nathan Vasse 51902cc639 ♻️(frontend) pass the import error as importErrorMessage
Sending a whole Alert through importModalChildren was a workaround
while ui-kit snapshotted importErrorMessage on click. Since 0.28.1
the prop is read at render time, so a plain string set from the
onImportContacts error handler is enough and the modal renders the
error in its own style.
2026-08-03 17:16:51 +02:00
Nathan Vasse f1db51f08a ⬆️(frontend) bump ui-kit to 0.28.1
The share modal now reads importErrorMessage at render time instead
of snapshotting it when the import button is clicked, so an error set
from the consumer handler shows up on the failing attempt rather than
one attempt late.
2026-08-03 17:16:51 +02:00
Nathan Vasse 92199e0fcc 🐛(frontend) stop drag events leaking out of the share modal
The share modal is portaled to the body but React still bubbles its
drag events up the component tree, so dragging a contacts file over
the import zone raised the explorer "drop your files here" toast
behind the modal. Wrap the modal in a barrier stopping drag events,
which also prevents a missed drop from sending the file to the
current folder.
2026-08-03 17:16:51 +02:00
Nathan Vasse 60fe2236c8 📝(changelog) mention the storage quota exclusion features
The changelog was missing the entries for the quota_excluded flag, the
per-audience item attributes and the grant_unlimited_storage command.
2026-08-03 10:20:45 +02:00
Nathan Vasse c2412ae6dd (backend) add a grant_unlimited_storage command
When enabling storage quotas on an existing instance, users already
above the limit would be blocked overnight. This command grandfathers
them by setting an unlimited storage override (0) for every user above
a given threshold, expressed in decimal GB.

The size aggregation uses the same filter as the storage compute
backend so the command agrees with the quota enforcement. Users with an
existing override are skipped to never clobber an explicit per-user
decision, and a dry-run mode allows previewing the affected users.
2026-08-03 10:20:45 +02:00
Nathan Vasse 8f47646dff (backend) apply per-audience attributes to external api items
External services consuming the resource server API may need items they
create to carry specific attributes, typically quota_excluded so their
uploads do not consume the user quota.

The new EXTERNAL_API_AUD_ITEM_ATTRIBUTES setting maps a token audience
to the attributes applied at creation. The lookup is a hook on the item
viewset overridden only in the resource server viewset, so both the
root create and children creation paths are covered while the regular
API remains unaffected.
2026-08-03 10:20:45 +02:00
Nathan Vasse 9caa1caa3d (backend) add a quota_excluded flag on items
Some items will be created by external services on behalf of users and
should not count against their creator's storage quota. Items flagged as
quota_excluded are now ignored by the creator storage compute backend.

The flag is part of the fields invalidating the cached storage used so
toggling it is reflected immediately in the entitlements.

The covering index is replaced by one whose condition also filters out
quota excluded rows, keeping the aggregation index-only. Both migrations
are safe on a large table: the AddField is catalog-only on PostgreSQL
11+ and the index swap runs CONCURRENTLY, adding the new index before
removing the old one.
2026-08-03 10:10:34 +02:00
Julien Maupetit 9b1bc4b5cc 🚚(global) move favorite items API endpoint to /items/favorites/
To respect the globally used pattern, we can safely switch to a simpler
path.
2026-07-31 16:54:17 +02:00
Julien Maupetit d4a8c4585f 📝(backend) fix documented trashbin API endpoint
Endpoint does not target a specific item.
2026-07-31 16:54:17 +02:00
Nathan Vasse f239b58e4d (frontend) adapt share e2e helper to the ui-kit 0.28 modal
ui-kit 0.28.0 replaces the share modal plain quick search input with an
invite field: selected users become chips next to a role dropdown and a
dedicated share button. The helper still targeted the old combobox label
and page-level share button, breaking every spec sharing an item.
2026-07-28 12:00:32 +02:00
Nathan Vasse c45b0943c5 (frontend) mock saxen in jest config
ui-kit 0.28.0 pulls in read-excel-file, whose CommonJS build requires
saxen, an ESM-only package that jest cannot parse. No unit test parses
spreadsheets, so a stub parser is enough, following the pretty-bytes
precedent.
2026-07-28 11:43:12 +02:00
Nathan Vasse ab8e0ebc39 (frontend) share an item with contacts imported from a file
Plug the ui-kit share modal file import onto the new batch share
endpoint. The option only shows up when ALLOW_SHARE_IMPORT_FILE is
enabled in the config. Errors are rendered inside the import modal
instead of the global toast so the user can fix the file and retry
without losing context.

The e2e environment enables the flag to cover the flow end to end.
2026-07-28 11:37:10 +02:00
Nathan Vasse 41e796b2a9 ⬆️(frontend) bump ui-kit for the share import modal
The share import modal needs ui-kit latest version.
2026-07-28 11:37:10 +02:00
Nathan Vasse 16f11c12e7 (backend) add an item batch share endpoint
Sharing a folder with many contacts one call at a time is slow and can
leave a partial state when one of them fails. This endpoint accepts a
list of email/role rows, validates them all before any write, then
creates accesses for known users and invitations for unknown emails.
Rows targeting users already covered by an equal or higher role are
skipped and reported so the client can inform the user.

The feature is disabled by default and gated by the new
ALLOW_SHARE_IMPORT_FILE setting exposed in the config endpoint.
2026-07-28 11:26:07 +02:00
Nathan Vasse 22c7eac3aa ♻️(backend) move descendants access sync to a service
The descendants access synchronization logic was private to the accesses
viewset. Extracting it into core/services/accesses.py lets the upcoming
batch share endpoint reuse it without reaching into viewset internals.
2026-07-28 11:25:22 +02:00
Nathan Vasse 9e080f2716 (frontend) cover the homepage messages widget button
The homepage shows the messages widget button when the widget is
configured. A test ensures the loader init command is queued and the
loader script is injected, so a config regression cannot silently
remove the button.
2026-07-28 09:41:07 +02:00
Nathan Vasse 9b545b6087 (frontend) show the messages widget button on the homepage
Anonymous visitors have no help menu, so the homepage loads the
widget floating button instead when the widget is configured. This
gives them the same support entry point as logged-in users.
2026-07-28 09:41:07 +02:00
Nathan Vasse 26197545af (frontend) add a button mode to the messages widget hook
The homepage needs the widget floating button while the help menu
opens the form directly. A WidgetHelper now owns the command queue
and script injection so both entry points share the same bootstrap,
and it fills the legacy queue key so older widget runtimes keep
working. The hook exposes showButton to init the loader script and
canLoadWidget so callers can check the config before rendering.
2026-07-28 09:41:07 +02:00
Nathan Vasse 9120c819ec (frontend) open the messages widget from the help menu
Some instances use the messages widget for support instead of a plain
mailto link. A new supportMessagesWidget flag in the help menu config
lets the "Contact us" entry open the widget, taking precedence over
the support email when both are configured.
2026-07-27 16:35:39 +02:00
Nathan Vasse 635135d1ed ♻️(frontend) reuse the left panel footer on public layouts
The home and simple layouts rendered their own mobile-only panel with
just the gaufre. Reusing the explorer footer keeps the settings entry
and user profile consistent everywhere and removes the duplicate
component. The index page is also reformatted by prettier on the way.
2026-07-23 17:08:12 +02:00
Nathan Vasse d42dd3061a (frontend) refresh the storage gauge on move and duplicate
Duplicating an item consumes storage and moving an item to or from the
root can change who owns its usage. Invalidate the entitlements cache
on success so the gauge reflects the new usage without a reload.
2026-07-23 17:08:12 +02:00
Nathan Vasse 826ad05ce6 🎨(backend) assign the quota with a walrus operator
Apply a review suggestion to keep the quota lookup and its guard on a
single line in the entitlements endpoint.
2026-07-23 17:08:11 +02:00
Nathan Vasse 060707c037 ♻️(backend) invalidate the storage cache from Item.save
Following review, replace the post_save signal with a direct call in
the model method. The invalidation logic stays close to the domain and
is easier to trace than an implicit signal. Bulk queryset updates keep
their explicit invalidation since they bypass save().
2026-07-23 17:08:11 +02:00
Nathan Vasse 1a7fcd4198 ✏️(frontend) fix the spelling of quota exceeded codes
Align the driver enums, quota state, translation keys and e2e fixtures
with the backend error codes now spelled "exceeded".
2026-07-23 17:08:11 +02:00
Nathan Vasse 1d14ef01ba ✏️(backend) fix the spelling of quota exceeded codes
The entitlements enums exposed "excedeed" in both their names and their
wire values. Fix the spelling now, while the codes are only consumed by
this branch, as they are part of the API contract with the frontend.
2026-07-23 17:08:11 +02:00
Nathan Vasse bac1e2dd53 (backend) push usage metrics to DeployCenter entitlements requests
DeployCenter now accepts the usage metrics directly in the entitlements
request body, so we switch from GET to POST and send a "usage_metrics"
list with the user's storage and the aggregated storage of the active
users sharing the same organization claim. This avoids the extra HTTP
roundtrip DeployCenter previously made to our usage metrics API when
resolving entitlements.

The entries reuse the Usage serializers so the pushed body and the
pulled external API cannot drift. The organization serializer now takes
the users queryset to be usable from both call sites, which also moves
the storage computation out of the viewset. The organization claim key
is configurable through a new "organization_claim" backend parameter
defaulting to "siret".
2026-07-23 17:08:11 +02:00
Nathan Vasse d3d9dff6b6 📝(changelog) mention the storage gauge and reorder entries
The frontend storage gauge deserved its own changelog entry, and the
local backend line moves after the already released entries to keep
the section in insertion order.
2026-07-23 17:08:11 +02:00
Nathan Vasse 24863e9e74 (frontend) keep translations in sync across languages
Several keys were missing in some languages (Dutch search filters,
delete menu labels, restore toasts, file picker caption), silently
falling back to English. Sync them and add a test failing whenever a
key exists in one language but not the others, so gaps are caught at
review time instead of in production.
2026-07-23 17:08:11 +02:00
Nathan Vasse be6109c99f (frontend) add the storage gauge and settings modal
Renders the quota returned by the entitlements API as a gauge in the
left panel footer, with a locked state when the organization quota
is reached and a tooltip carrying the error when the quota cannot be
computed. Clicking it opens a settings modal with the detailed gauge
and an optional documentation link. The footer is reworked to host
the gauge on mobile too (user profile and app grid move there), and
the entitlements query is refetched after uploads and hard deletes
so the gauge follows the usage.
2026-07-23 17:08:11 +02:00
Nathan Vasse 027433253f (frontend) show specific quota messages on rejected actions
Uploads, moves to the root and duplications can now be refused by the
backend quota gates. The API error code carries the can_upload reason,
so each surface (upload list, move toast, duplicate toast) maps it to
a dedicated translated message instead of a generic failure, and the
40x redirect is disabled on those calls so the user stays in place
and sees the toast. The move mutation handles its own error feedback
to avoid double toasting through the global handler.
2026-07-23 17:08:11 +02:00
Nathan Vasse 5ed2639e61 ⬆️(frontend) upgrade ui-kit to 0.27.0
Brings the StorageGaugeButton and StorageGaugeInformation components
needed to render the storage quota gauge.
2026-07-23 17:08:11 +02:00
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