The backend now rejects the creation, and the 403 is already toasted,
but letting the user name a document only to have it refused is a poor
way to learn about a quota. Surface the reason on the entries instead.
Entitlements are assumed permissive while the query is in flight, so
the menu does not grey out on every page load. "New folder" stays
enabled since folders are not quota gated.
The can_upload entitlement was only checked when creating a child, so
creating a file through POST /items/ escaped it entirely. The "+ New"
button takes that path whenever the current folder is not writable,
which is the case on "My files", so an over quota user could still
create documents from there.
Template based creation made it worse: it stores the file and marks the
item READY itself, so upload-ended, the only other gate, never runs.
Move the check into a helper shared by both create paths so they cannot
drift apart again.
In the default compose setup, .
/src/frontend/node_modules and
./src/frontend/apps/drive/node_modules are bind-mounted to the host
filesystem. If these directories are empty, mounting them into the
frontend-dev container masks the dependencies built into the Docker
image, causing runtime failures (e.g. missing pdfjs-dist/package.json).
This commit:
- Uses the frontend-dev container (FRONTEND_YARN) to install
dependencies via 'yarn install --frozen-lockfile' without requiring
host Node.js or Yarn.
- Adds the containerized yarn install step directly into the
build-frontend target, ensuring host node_modules are populated during
image build / make bootstrap.
- Leaves frontend-development-install untouched for host-only developers.
Signed-off-by: rcutte <rcutte@student.42.fr>
Every e2e project forces an en-US locale, so nothing exercised a browser
announcing a language without a region. The new test walks to the page a
public share link opens on an office file, where the regression showed.
Browsers may announce a language without a region: Firefox in French
sends "fr" where Chrome sends "fr-FR". Our resources were keyed by bare
language and we declared no supported languages, so i18next kept "fr"
as-is. The design system only ships "fr-FR" and fell back to English,
which is why the intermediate page of an office file opened from a
public link was in English while the interface was in French.
The same mismatch prevented the language from being synced to the
backend, so the online editor opened in English too.
Key the resources the way Django does and declare them as supported, so
any incoming tag resolves to a language we actually ship.
The list of languages we ship lived in the header component, forcing a
hook and the anonymous user menu to import from a component. Declaring
it next to the i18n configuration gives a single place to add or remove
a language.
Deutsch is dropped: we ship no German translations, so selecting it left
the interface in English with the design system in German.
The picker now reads the active language from i18next rather than from
the user record, which stays null until the language is synced.
MinIO stopped publishing images on Docker Hub in October 2025, so
minio/mc and minio/minio can no longer be pulled and createbuckets
fails. The same images are still served from quay.io.
useGetQueryKeyToRefresh invalidated only ["items", "infinite"] (+ the folder
["items", <parentId>, "children"]). React Query invalidation is prefix-based,
so that matches the general list views (My files, Shared, Favorites) but not
the Recent view, whose key is ["items", "recent", "infinite"]
(useInfiniteRecentItems). As a result, a mutation performed while viewing
Recent (create, convert, delete, rename, upload) did not refresh the list
until a manual reload. Also invalidate the recent key.
Invoking `$(MAKE) target-rule` in a rule definition where it can be used
as a rule dependency seems less canonical and decrease readability.
Let's fix this!
Running end-to-end tests locally can be a burden as there are many
services to start and configure. It can be improved by avoiding to
restart the whole stack at every test run.
We've also added documentation and fix permission issues regarding
volumes and locally created files and directories.
Added
- 📈(backend) add a Sentry performance monitoring sample rate setting
- ♻️(backend) route permission decisions through a swappable backend
- ✨(backend) add restricted access on folders, detached behind a restriction
Changed
- ♻️(frontend) migrate to the merged @gouvfr-lasuite/ui-components package
Fixed
- 🐛(frontend) render the JPEG 2000 layers of scanned PDFs in the preview
- 🐛(backend) prevent item deletion by a creator whose access was revoked
- 🐛(backend) resolve the direct parent by exact path after a move
1.1.2 takes every calendar hook from @react-aria/calendar instead of
splitting them between that package and the react-aria umbrella. The
WeakMap those hooks share is no longer cut in two when the tree carries
more than one copy, so the date filter stops crashing without us having
to pin that dependency cluster ourselves.
ui-components 1.1.1 looks for them at the site root, and pdf.js skips
a missing one with a warning rather than failing, so scanned PDFs were
drawn from their JBIG2 mask alone, without the JPEG 2000 layers.
Copying from the pdfjs-dist that react-pdf resolves keeps them on the
worker version, and 3.2 MB of generated data out of the repository.
UI Kit and Cunningham are now published as a single package, with no
compatibility release under the former names. The import rewrite is
the output of `ui-codemod@1.0.0 migrate . --source all`.
Material Icons used to ride along with the ui-kit fonts bundle, whose
replacement ships only the text fonts, so it now needs an explicit
import; without it every icon renders as a blank glyph.
Restrictions are tree entries, not content: they never match a search,
never reach the search index, and leave no entry in an exported
archive. The target itself is indexed and exported through its own
root, so users excluded from a restricted folder cannot find its
content through search or an ancestor export.
An explicit owner trashing a restricted folder leaves no entry
pointing into the trash. Restoring the folder brings the restriction
back, unless the containing subtree went to the trash meanwhile. Hard
deleting the folder takes its restriction along. A restriction trashed
with its container follows the container's restore, its target staying
untouched at the tree root.
Deleting a restriction removes the entry from the containing folder
without trashing anything. An owner excluded from the target acts on
the container only and can neither destroy nor read it: the folder
keeps its accesses and surfaces in its members' top-level listing as
a regular root folder.
A restricted folder lives at the tree root but its members reach it
through the restriction when they can open the containing folder. The
listing hides the root in that case so the folder shows up in a
single location, and keeps it for members without container access.
Restrictions expose their target's id, title and a can_access flag so
the frontend can grey out entries pointing to folders the user
cannot open. The children listing prefetches the viewer accesses to
keep the query count flat, and the tree includes restriction entries.
Owners activate and deactivate restriction on folders through a
dedicated restrict action, gated by the restrict ability. The
serializer keeps working on the instance returned by the toggle
since the item physically moves.
The reach kept from the restriction period is reset to inherit when
the reattached parent already grants as much or more. A more open
explicit reach survives, matching the role normalization rule.
Explicit roles granted during restriction that are now covered by
inheritance are dropped, so the sharing screen does not keep dead
entries. Superior roles and roles without inherited counterpart stay.
The folder returns under its restriction's current parent and the
restriction disappears. Without a live restriction the folder stays a
detached root. Inheritance applies again through the tree structure.
Restriction is structural: the folder physically leaves its parent
so inheritance stops applying without any query-level cut. A
restriction materializes its origin location. Explicit link reach is
kept and defaults to restricted only when it was inherited. The acting
owner gets an explicit owner access so the folder stays reachable once
cut from its parent. A restricted folder cannot be moved, nor a
restriction under its own target.
Only an explicit owner can toggle restriction on a folder. A folder
needs a parent to host its restriction on activation, while an already
restricted folder lives at the tree root and must stay deactivatable.
Listings annotate the restriction presence so the ability stays free
of extra queries.
A restriction materializes the original location of a restricted
folder moved to the tree root. The OneToOne target enforces a
single restriction per folder and a DB constraint ties the target
to the restriction type. A folder is restricted while a restriction
targets it. Restrictions cannot be created through the API: they only
appear by restricting a folder.
TreeModel.parent() uses .last() which follows Meta.ordering
(created_at). After a move(), the grandparent created later
is returned instead of the direct parent. The parent path is
known statically, so look it up by equality instead of
scanning ancestors.
The soft delete assertion sat inside the pytest.raises block and
never ran, hiding a wrong expected message. Indexer error tests
now keep a single raising invocation inside the block so the
failure source is unambiguous. Flagged by Sonar on PR #756.
Sonar flags the cognitive complexity of LinkItemSerializer.validate.
Move the checks against the ancestors' link definition to a helper so
validate only handles the required field; behavior is unchanged.
Each ability now reads as a named rule on ItemAbilities, fixing
the Sonar S3776 complexity of the former monolithic function.
Per-action methods also sketch the vocabulary a future ABAC
engine will implement, one check per action.
Move role and access resolution behind a backend resolved from
the new PERMISSIONS_BACKEND setting, following the entitlements
backend convention. Behavior is unchanged; this gives a single
decision point to swap for an ABAC engine later.
Allow login with demo users in local development. Fix Paige Turner's
email and replace the Wade Wilson demo user with Mark Down so every
demo user exists in the realm.
The mixed scenario writes on every session, while real drive traffic is
massively read-dominant. This variant keeps the same building blocks but
only UPLOAD_PCT percent of sessions (15 by default) go through the write
path; every session browses root items, recents, shared-with-me and
navigates into a randomly picked folder and subfolder.
To make reads meaningful despite empty fresh accounts, the first session
of each user always uploads the fixture folder tree and keeps it for the
whole run. Seed folders are left behind on purpose: the target instance
is reset by ops (database and bucket) between test campaigns.
We need to measure backend response times and error codes from 100 up to
100k simultaneous users, on any instance. The scenario simulates a full
user session (browse, recents, shared-with-me, folder creation, upload,
trash, hard-delete) with randomized think times, using the e2e auth
endpoint instead of the IDP, out of scope like the WOPI editors.
Everything is property-driven (BASE_URL, USERS, USER_OFFSET...) so the
same plan runs unchanged against any instance and across distributed
injectors, each with its own USER_OFFSET to keep generated user emails
globally unique. Uploads reproduce the frontend flow (item creation,
presigned PUT to the object storage, upload-ended) and randomly pick a
fixture entry; folder entries mirror the frontend folder upload by
materializing the hierarchy before uploading each file into its parent.
Load-test scenarios authenticate through the e2e user-auth endpoint, which
must be available on a production-like instance without enabling DEBUG.
LOAD_E2E_URLS is kept as a plain literal so no environment variable can
ever flip it, and post_setup refuses to start any configuration outside an
explicit allowlist that would expose it. Deploying with
DJANGO_CONFIGURATION=LoadTest becomes the only way to expose these
endpoints on a Production-derived setup, making it a deliberate and
reviewable infrastructure decision.
Expose SENTRY_TRACES_SAMPLE_RATE to enable Sentry tracing.
Defaults to 0 so existing deployments are unaffected; production
can opt in with a low sampling ratio to monitor performance.
Since delete by the creator is now gated on holding editor access,
a creator downgraded to reader on the root loses destroy and
hard_delete on the sub items they created. Update the stale
expectations and fix ruff formatting on the factory call.
The delete endpoint performs a soft delete, so Item.objects.count()
never changes whether the request succeeds or fails. The assertion
gave a false sense of protection; the deleted_at check is the one
that actually verifies the item was not touched.
Deleting an item relied on the creator field alone, which is an
immutable historical reference. A user could still delete an item
they created inside a shared folder after their access was revoked,
wiping the whole subtree including the owner's content.
Gate the creator branch on holding editor access so the ability is
lost as soon as access is revoked, matching the update permission.