Commit Graph

200 Commits

Author SHA1 Message Date
Edouard Vanbelle e2b5be6862 security(webdav+nc): antienum (404) rather returning a 500 with reason 2026-07-19 10:37:34 +02:00
M.Schmidt a01b0a856e fix error mapping for put; cross surface interop ressource locking; put quota leftovers; 2026-07-19 10:36: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
M.Schmidt 93ae7ab142 feat(webdav): extend HTTP PATCH to the NextCloud surface (RFC 5789)
The NextCloud-compatible WebDAV surface (/remote.php/dav/…) had no PATCH
dispatch arm at all — requests fell through to 405 — unlike the plain-file
surface (see the sibling commit on this repo's rfc-5789-http-patch work).

Adds handle_patch to nextcloud/webdav_handler.rs, reusing the plain
surface's X-Update-Range mechanism directly instead of duplicating it:
- api/handlers/webdav_handler.rs::parse_update_range is now pub(crate)
  so both surfaces share the same header-parsing/validation logic.
- upload_ingest::ingest_range_patch_to_cas (already surface-agnostic)
  splices the request body between the file's untouched prefix/suffix
  byte ranges and re-ingests through the same content-addressable
  pipeline handle_put uses.

Follows this file's own handle_put conventions rather than the plain
handler's: no active-lock guard (the NC surface has no LOCK/UNLOCK
dispatch arm at all) and no explicit storage-quota check (handle_put
doesn't do one either on this surface) — matching the sibling handler
instead of importing behavior the NC surface doesn't otherwise have.

Adds PATCH to the OPTIONS Allow header.

Also fixes a pre-existing clippy::useless_borrows_in_formatting warning
in thumbnail_service.rs (unrelated to this change, but blocking a clean
clippy run on this branch).

Adds tests/api/nc_webdav_patch.hurl covering explicit-range and append
PATCH, the Content-Range rejection, the missing-header 400, and PATCH
on a nonexistent file.
2026-07-19 10:35:35 +02:00
M.Schmidt d3546305f6 feat(webdav): implement HTTP PATCH for partial content updates (RFC 5789)
RFC 4918 §9.7.1 forbids partial updates on PUT; this adds PATCH as the
supported mechanism instead, via an X-Update-Range header (bytes=<start>-<end>
or append). Reuses the existing CAS/dedup pipeline by splicing the request
body between the file's untouched prefix/suffix byte ranges and re-ingesting
as one continuous stream, so unedited chunks dedup for free.
2026-07-19 10:35:35 +02:00
Markus Schmidt 7f04236c4b Merge branch 'main' into idp-auto-redirect 2026-07-18 20:16:54 +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
Edouard Vanbelle fa0e4e1a89 fix(cache): invalidate drive used byte cache on explicit refresh from internal call
this fix https://github.com/AtalayaLabs/OxiCloud/issues/607
    which was introduced by commit 12dc648cff
    when a user does activity in a drive, admin can invalidate cache via the internal
    call /api/admin/internal/trigger-sweep
    this permit end 2 end test to validte immediately that used_bytes corresponds to the expected result
2026-07-17 20:20:09 +02:00
Edouard Vanbelle dc009f053e security(nextcloud): ocs: get only users profile session can access to 2026-07-17 20:20:09 +02:00
Edouard Vanbelle 9e30018134 security(/api/admin): require admin by default
this is security by default: all routes attached to /api/admin
    will be by default authn + authz admin only
2026-07-17 20:20:05 +02:00
Edouard Vanbelle b1276938d4 security(upload): add permission to upload_file_streaming() 2026-07-17 19:01:05 +02:00
Edouard Vanbelle dd72b77c22 security(search): move DELETE /search/cache to protected path 2026-07-17 19:01:05 +02:00
Edouard Vanbelle eb884f6c8f fix(contact): use Permission::Create for creations 2026-07-17 19:01:05 +02:00
Edouard Vanbelle 38abe6766c fix(contact): use Permission::Delete for deletion verb 2026-07-17 19:01:05 +02:00
Edouard Vanbelle 20b1ea1a6a security(nc-uploads+trash): close #12 chunked-upload create bypass; graduated denial on empty-trash-for-drive
- nc chunked-upload MOVE assembly (#12): both branches now funnel through
  update_file_streaming_with_perms, whose internal fork enforces Update on
  the existing file OR Create on the parent folder / drive root. Pre-fix,
  the create branch went through plain upload_file_streaming with no
  authz.require — a Viewer on a shared drive could MKCOL → PUT chunks →
  MOVE and land a brand-new file. Error mapping switched to AppError::from
  so denials keep the graduated 403/404 shape.

- trash empty-for-drive: route through authz.require(Delete, Drive) instead
  of the bespoke drives_with_delete_for check + hardcoded not_found. Viewer
  now gets 403 (has Read), outsider stays 404 (no Read, anti-enum). Emits
  the standard authz.denied event with visibility field instead of the
  ad-hoc trash.empty_drive_rejected.

- tests/api/trash_per_drive.hurl: flip Viewer/Editor asserts 404 → 403;
  new Step 11b regression pin for finding #10 (Editor restore + delete
  attempts must 403 AND body must not contain "success":true — trips if
  the historical substring-match-on-"not found" hack ever comes back).
2026-07-17 19:01:05 +02:00
M.Schmidt 6215f37bf6 fix: dedupe IdP auto-redirect and fix CSP/bfcache bug on the SPA shell
- Extract tryAutoRedirectToIdp() on the login page so onMount's redirect
  guard and the post-setup flow share one check instead of drifting.
- Fix a real bug: 304 Not Modified responses carry no Content-Type, so
  is_html misclassified them and attached the strict headerless CSP,
  which browsers merge into the cached 200's effective headers and
  defeat the SPA's hash-based CSP on revalidated repeat visits.
- Add Cache-Control: no-store on the SPA shell to opt out of bfcache,
  preventing a pre-deploy shell (stale inline hydration script + CSP
  hash) from being resurrected byte-for-byte across the OIDC redirect's
  full-page navigations.
- Add a manual, human-run SSO-only script/env (ports 8090/1081) since
  the automated oidc.hurl suite keeps password login enabled and never
  exercises the auto-redirect guard.
2026-07-16 23:16:22 +02:00
Edouard Vanbelle 7aea383588 feat(antienum): 403 when sub can read, 404 otherwise
this is a UX improvement, always return a 404 not found when subject do not have any access on the resource
    but returns an explicit 403 forbidden is subject try a forbidden action on a resourse it can read

    regarding performance, the role is already in cache for the second call with read perm
2026-07-16 22:09:45 +02:00
Edouard Vanbelle 5b996bb218 security(webdav+nc): antienum (404) rather returning a 500 with reason 2026-07-16 21:25:13 +02:00
Edouard Vanbelle c1924c825b security(search): ensure that search suggenstion returns answer the user has access to 2026-07-16 21:07:18 +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
Edouard Vanbelle 7de83de0a9 chore(ci): wire caldav/carddav test suite 2026-07-15 07:55:31 +02:00
Edouard Vanbelle cb6c29a063 fix(caldav): fix generation of events
keep information of: ATTENDEE, ORGANIZER, CATEGORIES, STATUS, TRANSP, VALARM, X-*

    this fix answer in all calldav GET
2026-07-15 07:55:31 +02:00
Edouard Vanbelle e360d093bb test(caldav+carddav): rm xfail mark on known cases 2026-07-15 07:55:31 +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
Dionisio Pozo bad3abba27 Merge pull request #590 from EdouardVanbelle/test/caldav 2026-07-14 23:12:27 +02:00
Dionisio Pozo 4336cf97b4 Merge pull request #589 from EdouardVanbelle/fix/caldav-carddav-error-mapping 2026-07-14 23:12:11 +02:00
Edouard Vanbelle dc2c8f5dcd fix(carddav): fix tel uri 2026-07-14 23:00:19 +02:00
Edouard Vanbelle ad85fd5b91 fix(caldav+carddav): return correct error rather 500 2026-07-14 22:27:30 +02:00
Edouard Vanbelle a9e05ec47c test: full caldav + carddav test suite 2026-07-14 22:01:31 +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 341e354162 test(frontend): correct test to fit sign up/in 2026-07-14 03:27:28 +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
Edouard Vanbelle 01da450cf6 feat(registration): add a domain allow list
add:
 - OXICLOUD_REGISTRATION_ALLOWED_EMAIL_DOMAINS to specify list of domains allowing a self registration
 - OXICLOUD_REQUIRE_VERIFIED_EMAIL=true|false
 - OXICLOUD_AUTH_METHODS=password,magic_link (login methods, OIDC is on top of this)
 - OXICLOUD_AUTH_POLICIES=permit_magic_link_for_password_users (OIDC is on top)
2026-07-14 02:43:39 +02:00
Edouard Vanbelle b18f0dc74a test(front): isolate dotfile e2e fixtures under scratch parents 2026-07-13 22:00:53 +02:00
Edouard Vanbelle 063382ad60 test(front): test dotfile view/hidden 2026-07-13 21:21:26 +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
M.Schmidt e5c8d89da9 fix(webdav): bump storage usage on PUT, not just REST multipart upload
update_file_streaming_with_perms (the method behind every WebDAV/
NextCloud/WOPI PUT) never called the storage-usage-delta hook, so
drives.used_bytes and the RFC 4331 quota-used-bytes property never
reflected content written via WebDAV — only the REST multipart
upload path bumped usage. Extract apply_storage_usage_delta() from
maybe_update_storage_usage() and wire it into both branches: the
overwrite path applies new_size - old_size, the create path applies
the full size.

Also fixes the two RFC 4331 hurl tests that caught this:
nc_webdav_quota_properties.hurl had a Hurl parse error ([BasicAuth]
section keys can't mix literal+template, so the {user}~{folder}
composite marker is now pre-resolved via [Options] variable: before
being referenced as a single template), and both quota-properties
tests now retry the post-upload PROPFIND (matching the existing
drive_quota.hurl/user_envelope_quota.hurl pattern) since the delta
is applied fire-and-forget on a background task.
2026-07-13 20:00:01 +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