[FIX] dms: scope directory share tokens to the token's own subtree - #32
Open
Hotdgo9 wants to merge 2 commits into
Open
[FIX] dms: scope directory share tokens to the token's own subtree#32Hotdgo9 wants to merge 2 commits into
Hotdgo9 wants to merge 2 commits into
Conversation
dms.file.check_access_token compared the directory walker against self.directory_id instead of against the directory owning the token, so both the loop test and the "fix last level" test were self-comparisons that are always true. The branch collapsed to `return True` whenever any directory carried the supplied token, granting read access to every dms.file in the database regardless of directory or group inheritance. Compare against item.id, matching the correct implementation in dms.directory.check_access_token, and sudo the walk so ancestors the caller cannot read are still traversable. _get_files in the portal controller relied on the always-true behaviour: it called check_access_token on an empty dms.file recordset, where False == False happened to authorise the shared listing. Validate the token against the directory being browsed instead, mirroring the pattern already used by _get_directories. Adds regression tests covering unrelated trees, root-level files, descendant directories and a file's own token. Assisted-by: Claude Opus 5
The file-side walk previously returned on its first iteration because of the self-comparison bug, so it never actually traversed. Now that it does, a parent cycle would spin forever in a request reachable without authentication. _check_directory_recursion rejects cycles created through the ORM, so this only bites on corrupted data or direct SQL, but an unbounded loop on an anonymous endpoint is not worth leaving open. Track visited ids in both dms.file and dms.directory rather than imposing an arbitrary depth limit, so legitimate deep trees are unaffected. Assisted-by: Claude Opus 5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
check_access_tokenindms/models/dms_file.pywalks up the directory tree to decide whether the token's directory is the file's own directory or one of its ancestors — but both comparisons test the walker against itself rather than against the token's directory (item). At98f4088, L181 and L185:On the first iteration
directory_itemisself.directory_id, so it returns immediately; and if the directory has no parent the loop body never runs, at which point the "fix last level" line makes the same always-true comparison. The wholeif items:block reduces toreturn True— the token's own directory is never consulted. Stated once: the effect is that any valid directory share token authorises read of everydms.filein the database, irrespective of directory or group inheritance.dms.directory.check_access_token(models/directory.pyL241) does the same walk correctly, comparing againstitem.idat L251/L257/L261 — the intended logic is already in the tree next door.The portal hunk is mandatory, not optional
_get_files(controllers/portal.pyL162) callscheck_access_tokenon an emptydms.filerecordset at L187, whereself.directory_id.idisFalse. The loop is skipped and the always-trueFalse == Falseon the last-level line is currently what makes shared-folder listings work at all.Fix the model alone and every shared folder renders empty: HTTP 200, no traceback, nothing in the log. So the two hunks have to land together. The token belongs to a directory, so it is validated against the directory being browsed — exactly what
_get_directories(L193) already does._get_filesreturns early whendms_directory_idis falsy, so thebrowse()is always on a real id.Bounded walk
The ancestor walk is unbounded in both files. Before this fix the file-side loop always returned on iteration one, so it never actually traversed; once it does, a
parent_idcycle would spin indefinitely on a request that needs no authentication._check_directory_recursionrejects cycles created through the ORM, so realistically only corrupted data reaches it, but theseenset costs nothing.Tests
8 regression tests in
dms/tests/test_access_token.py— unrelated trees, root-level files, descendant directories, a file's own token, and the shared-folder listing the portal hunk protects. Red before, green after.Scope
This block is byte-identical on 12.0 through 18.0 and dates to the 13.0 migration, so the released branches carry it too. Landing it here means 19.0 never ships it. I am opening separate PRs against 17.0 and 18.0, where the patch applies verbatim; 16.0 and older need a hand-port because
_get_files/_get_directoriesdo not exist there.Disclosure
Being straightforward: the root cause and impact are already described in the commit body on our public fork. We found this auditing our own production instance and fixed it there before working out how to raise it upstream, and OCA/dms has private vulnerability reporting disabled — so by the time we got here there was no quiet route left. Flagging it rather than leaving you to find it. Our own instance had never created a share token, so we were never exposed.
Happy for you to take these commits under your own name if that keeps the migration moving.
Assisted-by: Claude Opus 5