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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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().
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.
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".
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.
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.
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.
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.
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.
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.
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.
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.
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.