Commit Graph

75 Commits

Author SHA1 Message Date
Edouard Vanbelle 390aa31443 feat(cli): merge oxicloud binary and cli
this feature to simplify the creation of only 1 binary for multiple architecture
2026-08-29 11:57:48 +02:00
Edouard Vanbelle 2760fe9efc refactor: rename tests on ref_count 2026-08-23 23:19:11 +02:00
Edouard Vanbelle 8d696ccc60 test(ref_count): check ref_count accross copy_folder and folder deletion 2026-08-23 23:19:11 +02:00
Edouard Vanbelle 811c7b0f12 feat(DPoP): add API test 2026-08-09 01:56:07 +02:00
Edouard Vanbelle 5418a5bd34 feat(opaque): add end to end test hurl + binary 2026-08-04 07:03:08 +02:00
Edouard Vanbelle 0e395ae15f feat(auth): bring opaque (RFC 9807) auth
OPAQUE (RFC 9807) implementation (using `opaque-ke` crate)

    with opaque authentfication, server will never receive the password (in the auth=password mode)
    this is a must have to create trust with users to permit end to end encryption in the future
    (we cannot know if user use the same password/passphrase for his asymetric key or his oxicloud auth,
    this is why server must never have the password)

    pass1: prepare server
2026-08-04 07:03:08 +02:00
Edouard Vanbelle 8329b4aa56 feat(storage): improve admin panel 2026-08-01 17:10:06 +02:00
Edouard Vanbelle 394708c9a1 feat(recov. job): add drive consistency service 2026-07-29 01:25:15 +02:00
Edouard Vanbelle 4ab57d0d8e test(job-registry): additional hurl coverage 2026-07-28 00:35:34 +02:00
Edouard Vanbelle 3b31b8911b feat(breadcrumb): build breadcrumb in 1 API call
add /api/folders/{id}/ancestors

    this API to iterate parent up to the drive root or the shared folder
    this will help UI to build the breadcrumb in 1 API call
    and to identify the root element (is it a drive users has access to or
    a shared folder ?)

    ui: now only 1 API call is now required to build the breadcrumb
2026-07-27 01:33:09 +02:00
Bradley Nelson 3ad38ddde7 test(mounts): admin CRUD hurl coverage + frontend mounts-tab tests
Close the two coverage gaps in the external-mounts feature — the admin
REST surface and the admin UI tab both previously had no automated tests.

Backend (hurl):
- tests/api/external_mounts.hurl — black-box coverage of the admin mount
  endpoints (list / create / delete): input validation (empty name,
  non-existent host path → 400), create → 201 with the full view, the
  mount appears in the list, delete → 204 then 404, and the /api/admin
  middleware denies a non-admin on every verb → 403.
- server.env: enable OXICLOUD_ENABLE_EXTERNAL_MOUNTS for the test server;
  run.sh: register the new scenario.

Frontend (vitest):
- admin/page.test.ts — opening the mounts tab loads + lists mounts,
  the create form calls createExternalMount, and delete goes through the
  confirm modal to deleteExternalMount.

Tooling:
- devenv.nix: add b3sum (used by tests/api/run.sh's storage-integrity check).
2026-07-21 18:19:03 -06:00
M.Schmidt ec2b533a53 security(webdav): adapt to anti-enum pattern 2026-07-19 19:55:26 +02:00
M.Schmidt d57f7bfe3a fix(webdav): wire orphaned PATCH tests + fix NC error-mapping and path bugs they caught
webdav_patch.hurl and nc_webdav_patch.hurl existed with real coverage
since the original PATCH commits but were never added to
tests/api/run.sh, so just api-test/CI silently skipped them. Wire both
in, fix nc_webdav_patch.hurl's header-after-[BasicAuth] ordering bug
that meant it had never actually passed, and add two new
consistency-focused files chaining PATCH operations with
cross-protocol/cross-surface verification:

- webdav_patch_consistency.hurl: chained overwrites with ETag-change
  checks, GET/HEAD/PROPFIND cross-protocol agreement, quota-507
  leaving the file byte-for-byte unchanged, direct_put_max_bytes
  prefix/suffix regression coverage.
- nc_webdav_patch_consistency.hurl: Editor/Viewer/Outsider permission
  matrix, cross-surface lock interop, quota-507 via the NC surface.

Running these surfaced two real bugs in the NC PATCH handler, both
fixed here:

- The write step mapped every error (including a legitimate anti-enum
  permission denial) to a raw 500 instead of AppError::from(e), unlike
  the plain surface. A Viewer without Update permission got a 500
  leak instead of the expected 404.
- nc_to_internal_path() didn't strip the leading '/' that chroot.path
  carries from StoragePath::to_string(), so a LOCK taken via /webdav/
  silently failed to block PATCH via /remote.php/dav/ on the same
  file — the exact-string lock-store lookup never matched. Added a
  regression unit test.
2026-07-19 10:35:35 +02:00
Edouard Vanbelle c2b5d9fe2e security(/api/dedup): normalize dedup admin routes into /api/admin
/dedup/stats       -> /api/admin/dedup/stats
    /dedup/recalculate -> /api/admin/dedup/recalculate
2026-07-17 21:51:48 +02:00
Edouard Vanbelle e0156a43f5 security(wopi): resolve PutFile drive_id from file, not caller's default 2026-07-17 20:43:42 +02:00
Dionisio Pozo b69c18b934 Merge pull request #598 from EdouardVanbelle/feat/readonly-drive 2026-07-16 07:40:29 +02:00
Edouard Vanbelle a6427fc028 feat(drive): add readonly policy
permmit admin to freeze a drive, trash janitor background job is also disabled for this drive
2026-07-16 01:02:15 +02:00
Edouard Vanbelle 53c301e472 fix(595): permit unlimited user quota 2026-07-15 22:21:51 +02:00
Dionisio Pozo e0b9e23201 Merge pull request #591 from EdouardVanbelle/fix/caldav-time-range-parser
fix(ical): fix timerange issue
2026-07-15 07:22:14 +02:00
Dionisio Pozo fc5a3a581f Merge pull request #592 from EdouardVanbelle/fix/carddav-parser-tel-adr
fix(carddav): fix tel uri
2026-07-15 07:21:57 +02:00
Edouard Vanbelle a67fcadeea fix(ical): fix timerange issue
accept iCal DATE-TIME + RFC 3339 fallback
2026-07-14 23:13:50 +02:00
Edouard Vanbelle dc2c8f5dcd fix(carddav): fix tel uri 2026-07-14 23:00:19 +02:00
Edouard Vanbelle 7966c7178a fix(528): pass3: PUT with RECURRENCE-ID 2026-07-14 20:42:18 +02:00
Edouard Vanbelle a7a45b3383 fix(caldav+carddav): raise 400 error on param issue
rather than a 500
2026-07-14 14:52:58 +02:00
Edouard Vanbelle 54b5b3bf4f feat(caldav+carddav): auto create default cal & card
automatically create default Calendar and default addressbook per user
    (no creation if user already have a such resource)

    default name are "Personal"

    this is using the user's life cycle like does the drives

    answers to issue #545
2026-07-14 14:30:05 +02:00
Dionisio Pozo 970f97b91a Merge pull request #570 from swissiety/rfc-4331-quota-properties
feat(webdav): RFC 4331 quota-available-bytes/quota-used-bytes
2026-07-14 12:58:22 +02:00
Edouard Vanbelle f331dbf0ee feat(account): upgrade external to internal 2026-07-14 11:10:23 +02:00
Dionisio Pozo 5ae551a93d Merge pull request #577 from EdouardVanbelle/feat/users-perfs-and-filter-dotfiles
feat: users prefs server side + filter dotfiles + filter shares by resource type
2026-07-14 09:07:09 +02:00
Edouard Vanbelle e94063d96a test(login/register): via password or magic-link
Password login

┌─────┬────────────────────────────────────────────────────┬────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────┐
│  #  │                        Case                        │         Where          │                                          Assertion                                          │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L1  │ Login by username                                  │ auth_login.hurl Case 1 │ 200 + access_token, user.email match                                                        │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L2  │ Login by email (dispatch on @)                     │ auth_login.hurl Case 2 │ 200, same session shape as L1                                                               │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L3  │ Bad password on username path                      │ auth_login.hurl Case 3 │ 403 anti-enum                                                                               │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L4  │ Bad password on email path                         │ auth_login.hurl Case 4 │ 403 anti-enum (same shape as L3)                                                            │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L5  │ Unknown username                                   │ auth_login.hurl Case 5 │ 403 anti-enum (same shape as L3)                                                            │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L6  │ Unknown email                                      │ auth_login.hurl Case 6 │ 403 anti-enum (same shape as L3)                                                            │
├─────┼────────────────────────────────────────────────────┼────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ L7  │ /api/auth/oidc/providers reports methods correctly │ auth_login.hurl Case 7 │ password_login_enabled: true, magic_link_login_enabled: true, require_verified_email: false │
└─────┴────────────────────────────────────────────────────┴────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────┘

Password registration

┌─────┬───────────────────────────────────────────────────┬──────────────────────────────┬─────────────────────────────────────────────────────────┐
│  #  │                       Case                        │            Where             │                        Assertion                        │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤
│ R1  │ Classic username + email + password → uniform 200 │ registration.hurl Step 2     │ anti-enum message contains "request received"           │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤
│ R2  │ Login after register works                        │ registration.hurl Step 2b    │ 200 + session for the new user                          │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────────┤
│ R3  │ Email collision → uniform 200 (no rewrite)        │ registration.hurl Steps 8-10 │ attacker password doesn't work; original account intact │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤
│ R4  │ Username collision → uniform 200                  │ registration.hurl Step 11    │ same anti-enum shape                                    │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤
│ R5  │ Off-domain rejection                              │ registration.hurl Step 12    │ 403 RegistrationDomainNotAllowed                        │
├─────┼───────────────────────────────────────────────────┼──────────────────────────────┼────────────────────────────┤
│ R6  │ Case-insensitive domain match                     │ registration.hurl Step 12b   │ uniform 200 on charlie@EXAMPLE.COM                      │
└─────┴───────────────────────────────────────────────────┴──────────────────────────────┴────────────────────────────┘

Magic-link registration (email-only signup)

┌─────┬──────────────────────────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────┐
│  #  │                                               Case                                               │             Where             │                   Assertion                    │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR1 │ Email-only signup → welcome mail queued                                                          │ registration.hurl Step 3      │ uniform 200 + browser-binding cookie set       │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR2 │ Welcome mail contains magic-link URL                                                             │ registration.hurl Step 4      │ captured from mock SMTP                        │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR3 │ PR 22 cross-browser confirmation page                                                            │ registration.hurl Step 5a     │ 200 HTML "different browser"                   │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR4 │ Cookie-bound redemption lands on SPA                                                             │ registration.hurl Step 5b     │ 302 → /files (SvelteKit route, post-migration) │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR5 │ email_verified_at stamped after redemption                                                       │ registration.hurl Step 6      │ field present on /api/auth/me                  │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR6 │ Second magic-link post-signup                                                                    │ registration.hurl Step 7      │ uniform 200                                    │
├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────┤
│ MR7 │ Profile PATCH — no-op, name set, empty-string rejected, username-taken 409, claim-once 409, etc. │ registration.hurl Steps 6a–6i │ full profile lifecycle                         │
└─────┴──────────────────────────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────┘

Magic-link login (existing account)

┌─────┬──────────────────────────────────────────────────────────┬──────────────────────────────────────┬───────────────────────────────────────┐
│  #  │                           Case                           │                Where                 │                             Assertion                              │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML1 │ Baseline password login still works                      │ auth_magic_link_login.hurl Steps 1-2 │ 200                                                                │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML2 │ magic-link/send with email identifier                    │ auth_magic_link_login.hurl Step 3    │ uniform 200 + cookie                                               │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML3 │ magic-link/send with username identifier (dispatch on @) │ auth_magic_link_login.hurl Step 4    │ uniform 200                                                        │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML4 │ Password-user policy: mail actually sent                 │ auth_magic_link_login.hurl Step 5    │ SMTP capture proves permit_magic_link_for_password_users in effect │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML5 │ Redemption creates a session                             │ auth_magic_link_login.hurl Steps 6-7 │ 302 → /files, /api/auth/me returns the same user                   │
├─────┼──────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────────────────────────────────┤
│ ML6 │ Anti-enum on unknown identifier                          │ auth_magic_link_login.hurl Step 8    │ same uniform 200 shape as ML3                                      │
└─────┴──────────────────────────────────────────────────────────┴──────────────────────────────────────┴───────────────────────────────────────┘

OIDC

┌─────┬────────────────────────────────────────────────────────────────────────┬───────────────────┬────────────────────────────────────────────────────────────────────────────────────────────┐
│  #  │                                  Case                                  │       Where       │                                                        Assertion                                                        │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O1  │ Setup local admin (bootstrap)                                          │ oidc.hurl Step 1  │ 201                                                                                                                     │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O2  │ Providers endpoint — OIDC visible                                      │ oidc.hurl Step 2  │ enabled: true, provider_name: MockSSO, password_login_enabled: true, magic_link_login_enabled: false (OIDC-master rule) │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O2b │ Magic-link/send refused (endpoint layer)                               │ oidc.hurl Step 2b │ 403 MagicLinkLoginDisabled — proves the policy gate fires, not a 503 SMTP-unwired                                       │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O3  │ Authorize redirect includes PKCE + state                               │ oidc.hurl Step 3  │ 307 to fake IdP                                                                                                         │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O4  │ IdP round-trip + JIT provisioning                                      │ oidc.hurl Step 4  │ Callback lands on /login?oidc_code=…                                                                                    │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O5  │ Code exchange → session cookies                                        │ oidc.hurl Step 5  │ 200 + all three cookies                                                                                                 │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O6  │ JIT profile mapping (name, given/family, picture, groups → admin role) │ oidc.hurl Step 6  │ every claim reflected on /api/auth/me                                                                                   │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O7  │ Refresh rotation on OIDC session                                       │ oidc.hurl Step 7  │ new access/refresh/CSRF cookies                                                                                         │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O8  │ Refreshed cookies authenticate                                         │ oidc.hurl Step 8  │ 200 on /api/auth/me                                                                                                     │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O9  │ Repeat login = same local user (no dup)                                │ oidc.hurl Step 9  │ user_id stable                                                                                                          │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O10 │ Anti-takeover: unverified email → refused                              │ oidc.hurl Step 10 │ 401/403                                                                                                                 │
├─────┼────────────────────────────────────────────────────────────────────────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤
│ O11 │ One-time code replay refused                                           │ oidc.hurl Step 11 │ second /exchange → 401                                                                                                  │
└─────┴────────────────────────────────────────────────────────────────────────┴───────────────────┴────────────────────────────────────────────────────────────────────────────────────────────┘

test
2026-07-14 03:16:25 +02:00
M.Schmidt 6701ddfc17 Merge branch 'main' into rfc-4331-quota-properties
# Conflicts:
#	src/interfaces/nextcloud/report_handler.rs
#	src/interfaces/nextcloud/webdav_handler.rs
#	tests/api/run.sh
2026-07-13 20:32:01 +02:00
Edouard Vanbelle 5aaf49859e feat(user-perf): add ui user-perf + dotfile filter
- add resource kind filter (file, folder, drive) in shared section (localStorage stored)
- add user preferences serverside store
- add client side dotfile filter (show/hide dotfiles) (user perf stored, default: dotfiles are shown)
  for security trashed dotfile are always displayed

  protection added: if a folder has only hidden items, a notification invite user to display it
  if a user rename or create a hidden item, a notification tells it to user
2026-07-13 20:27:52 +02:00
Edouard Vanbelle 05ef55a8e0 fix(nc): login OIDC + drive picker
ensure OIDC is supported during nextcloud login

flow is:

    1. nextcloud
    2. oxicloud login ( direct pass or OIDC according config)
    3. drive picker (if user has multiple drive)
    4. success page + backchannel login to nextcloud
2026-07-13 18:30:20 +02:00
Dionisio Pozo 06da428493 Merge pull request #572 from EdouardVanbelle/feat/nextcloud-chrooted-drive
feat/nextcloud chrooted drive
2026-07-13 09:37:10 +02:00
Dionisio Pozo 22e09c09be Merge pull request #538 from swissiety/webdav-litmus-compliance
implement dead properties for nextcloud handler and fixup frontend migration leftover
2026-07-13 09:36:43 +02:00
M.Schmidt c07aeabd85 feat(webdav): drive-aware RFC 4331 quota properties
resolve_quota only ever reported the caller's personal envelope,
ignoring the drive_id already resolved at every PROPFIND call site —
shared drives with their own quota showed the wrong numbers. Adds
AppState::resolve_webdav_quota, shared by both WebDAV surfaces:
nil drive_id or personal drive -> account envelope, shared drive ->
its own storage.drives quota/used_bytes.

Also adds quota-used-bytes/quota-available-bytes to the NextCloud-
compatible surface, which previously had no RFC 4331 support at all.

Registers webdav_quota_properties.hurl and the new
nc_webdav_quota_properties.hurl in tests/api/run.sh — neither was
wired into the suite before this change.
2026-07-13 00:34:17 +02:00
M.Schmidt 7011fdff5a Merge origin/main into webdav-litmus-compliance 2026-07-12 22:25:12 +02:00
Edouard Vanbelle 52814b4d7c fix(nextcloud): fix chroot + synchronisation
- add better hurl coverage on nextcloud chrooted login
    - fix issue with nextcloud using /{drive name}/~{drive id}/
    - fix trashbin handler

    confusion username vs {username}~{folder id}
2026-07-12 22:19:45 +02:00
Edouard Vanbelle ba620166ee feat(grant): clean up expired grants 2026-07-12 18:37:13 +02:00
Edouard Vanbelle c1e46910b0 feat(music): move playlist to authz engine 2026-07-08 01:04:03 +02:00
Edouard Vanbelle a2ad7757c3 feat(calendar,addressbook): add tests for authz 2026-07-08 01:03:25 +02:00
Edouard Vanbelle 7e34045ff8 feat(drive): fix webdav back-compat
add env variable `OXICLOUD_WEBDAV_DRIVE_LISTING_PREFIX`
    which is by default:
    `OXICLOUD_WEBDAV_DRIVE_LISTING_PREFIX="@drive"`

    so `/webdav/` -> points to user's personal drive (**backward compatibilit**y)
    `/web/dav/@drive/{uuid|drive name}/` points to the respective drive

    if admins want directly `/webdav/` pointing to list of drives they need to:
    `OXICLOUD_WEBDAV_DRIVE_LISTING_PREFIX=""`

    + ensure lock is per user (RFC 4918 §9.11)

    fix: #554
2026-07-06 22:14:50 +02:00
Edouard Vanbelle 75601beb43 security(wopi): add authz to Wopi 2026-07-05 23:31:44 +02:00
M.Schmidt 3501857a70 test(webdav): cover protected-property PROPPATCH rejection
Native + NC surfaces: DAV: displayname/getetag, oc:fileid/
permissions, nc:has-preview all 403 and never land in the store;
mixed request shows per-property granularity (protected prop 403
alongside an ordinary custom prop 200); oc:favorite regression
guard confirms its special-case still works despite being on the
protected list.
2026-07-01 22:54:56 +02:00
M.Schmidt 94e0145855 test(webdav): cover NC dead-props PROPPATCH/PROPFIND contract
Round-trip, upsert, remove, 404 on missing resource, survives MOVE,
reaped on DELETE, oc:favorite regression guard, folder coverage.
2026-07-01 19:59:23 +02:00
Edouard Vanbelle cf1479d113 test(webdav): cover nested folder move
this test is a duplicated with the move in litmus test suite
    it has been added because not everybody do have litmus installed
    and no clue if litmus will be kept in the future
2026-06-30 22:55:49 +02:00
Edouard Vanbelle 21ac3a178f test(webdav): cover the dead properties 2026-06-30 20:32:16 +02:00
Edouard Vanbelle 583af517c2 fix(drive): fix drive_id on copy to other drive
- fix alsot drive_id exposition
2026-06-29 23:21:17 +02:00
Edouard Vanbelle ee92d365b9 feat(drive): ensure drive_id updated on file|folder moved to another drive 2026-06-29 21:11:40 +02:00
Edouard Vanbelle ddb131da8b feat(drive): add policy forbid_public_links 2026-06-26 13:59:02 +02:00
Edouard Vanbelle cfd783cbd3 feat(drive): personal drives have the user's quota in commun 2026-06-26 13:59:02 +02:00