Skip to content

Conversation

@salmart-dev
Copy link
Contributor

@salmart-dev salmart-dev commented Aug 15, 2025

Summary

Sabre has an interface INodeByPath that allows nodes to fetch a specific node by its path. This was not implemented in our Directory class, leading to any file query to fetch all parent nodes until the file node was reached. This PR makes Directory implement that interface, extends makes the NC Node class extend the Node class from Sabre (required step).

Before:
Screenshot From 2025-08-15 18-52-24

After:
Screenshot From 2025-08-15 18-50-51

Checklist

@salmart-dev salmart-dev self-assigned this Aug 15, 2025
@salmart-dev salmart-dev force-pushed the fix/directoryAsINodeByPath branch from cad0ea3 to 87d9b88 Compare August 15, 2025 16:12
use OCP\Share\IManager;

abstract class Node implements \Sabre\DAV\INode {
abstract class Node extends \Sabre\DAV\Node implements \Sabre\DAV\INode {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment: unfortunately this is needed, as there is a check in Tree that checks for Node and not INode 😢 without this, the whole thing would not work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ideal. I this an overview in sabre code? Can you point to the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it's open source, so easy to change :) sabre-io/dav#1595

use OCP\Share\IManager;

abstract class Node implements \Sabre\DAV\INode {
abstract class Node extends \Sabre\DAV\Node implements \Sabre\DAV\INode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ideal. I this an overview in sabre code? Can you point to the code?

@salmart-dev salmart-dev force-pushed the fix/directoryAsINodeByPath branch from f67982e to c54b5c9 Compare August 21, 2025 16:28
@come-nc come-nc requested a review from icewind1991 August 25, 2025 09:05
@salmart-dev salmart-dev force-pushed the fix/directoryAsINodeByPath branch from c54b5c9 to cf952ce Compare September 10, 2025 11:02
@github-actions
Copy link
Contributor

Possible performance regression detected

Show Output
An unhandled exception has been thrown:
TypeError: array_map(): Argument #2 ($array) must be of type array, null given in /home/runner/work/server/server/apps/profiler/lib/Command/Compare.php:35
Stack trace:
#0 /home/runner/work/server/server/apps/profiler/lib/Command/Compare.php(35): array_map()
#1 /home/runner/work/server/server/3rdparty/symfony/console/Command/Command.php(326): OCAProfilerCommandCompare->execute()
#2 /home/runner/work/server/server/core/Command/Base.php(218): SymfonyComponentConsoleCommandCommand->run()
#3 /home/runner/work/server/server/3rdparty/symfony/console/Application.php(1078): OCCoreCommandBase->run()
#4 /home/runner/work/server/server/3rdparty/symfony/console/Application.php(324): SymfonyComponentConsoleApplication->doRunCommand()
#5 /home/runner/work/server/server/3rdparty/symfony/console/Application.php(175): SymfonyComponentConsoleApplication->doRun()
#6 /home/runner/work/server/server/lib/private/Console/Application.php(187): SymfonyComponentConsoleApplication->run()
#7 /home/runner/work/server/server/console.php(91): OCConsoleApplication->run()
#8 /home/runner/work/server/server/occ(33): require_once('...')
#9 {main}

1 similar comment
@github-actions
Copy link
Contributor

Possible performance regression detected

Show Output
An unhandled exception has been thrown:
TypeError: array_map(): Argument #2 ($array) must be of type array, null given in /home/runner/work/server/server/apps/profiler/lib/Command/Compare.php:35
Stack trace:
#0 /home/runner/work/server/server/apps/profiler/lib/Command/Compare.php(35): array_map()
#1 /home/runner/work/server/server/3rdparty/symfony/console/Command/Command.php(326): OCAProfilerCommandCompare->execute()
#2 /home/runner/work/server/server/core/Command/Base.php(218): SymfonyComponentConsoleCommandCommand->run()
#3 /home/runner/work/server/server/3rdparty/symfony/console/Application.php(1078): OCCoreCommandBase->run()
#4 /home/runner/work/server/server/3rdparty/symfony/console/Application.php(324): SymfonyComponentConsoleApplication->doRunCommand()
#5 /home/runner/work/server/server/3rdparty/symfony/console/Application.php(175): SymfonyComponentConsoleApplication->doRun()
#6 /home/runner/work/server/server/lib/private/Console/Application.php(187): SymfonyComponentConsoleApplication->run()
#7 /home/runner/work/server/server/console.php(91): OCConsoleApplication->run()
#8 /home/runner/work/server/server/occ(33): require_once('...')
#9 {main}

@salmart-dev
Copy link
Contributor Author

Performance regression warning is unrelated. The output of the check contains text which makes the json_decode part of the check fail and return null.

@salmart-dev salmart-dev added 3. to review Waiting for reviews and removed 2. developing Work in progress labels Sep 10, 2025
@github-actions
Copy link
Contributor

Possible performance regression detected

Show Output
An unhandled exception has been thrown:
TypeError: array_map(): Argument #2 ($array) must be of type array, null given in /home/runner/actions-runner/_work/server/server/apps/profiler/lib/Command/Compare.php:35
Stack trace:
#0 /home/runner/actions-runner/_work/server/server/apps/profiler/lib/Command/Compare.php(35): array_map()
#1 /home/runner/actions-runner/_work/server/server/3rdparty/symfony/console/Command/Command.php(326): OCAProfilerCommandCompare->execute()
#2 /home/runner/actions-runner/_work/server/server/core/Command/Base.php(218): SymfonyComponentConsoleCommandCommand->run()
#3 /home/runner/actions-runner/_work/server/server/3rdparty/symfony/console/Application.php(1078): OCCoreCommandBase->run()
#4 /home/runner/actions-runner/_work/server/server/3rdparty/symfony/console/Application.php(324): SymfonyComponentConsoleApplication->doRunCommand()
#5 /home/runner/actions-runner/_work/server/server/3rdparty/symfony/console/Application.php(175): SymfonyComponentConsoleApplication->doRun()
#6 /home/runner/actions-runner/_work/server/server/lib/private/Console/Application.php(187): SymfonyComponentConsoleApplication->run()
#7 /home/runner/actions-runner/_work/server/server/console.php(91): OCConsoleApplication->run()
#8 /home/runner/actions-runner/_work/server/server/occ(33): require_once('...')
#9 {main}

@github-actions
Copy link
Contributor

Possible performance regression detected

Show Output
An unhandled exception has been thrown:
TypeError: array_map(): Argument #2 ($array) must be of type array, null given in /home/runner/work/server/server/apps/profiler/lib/Command/Compare.php:35
Stack trace:
#0 /home/runner/work/server/server/apps/profiler/lib/Command/Compare.php(35): array_map()
#1 /home/runner/work/server/server/3rdparty/symfony/console/Command/Command.php(326): OCAProfilerCommandCompare->execute()
#2 /home/runner/work/server/server/core/Command/Base.php(218): SymfonyComponentConsoleCommandCommand->run()
#3 /home/runner/work/server/server/3rdparty/symfony/console/Application.php(1078): OCCoreCommandBase->run()
#4 /home/runner/work/server/server/3rdparty/symfony/console/Application.php(324): SymfonyComponentConsoleApplication->doRunCommand()
#5 /home/runner/work/server/server/3rdparty/symfony/console/Application.php(175): SymfonyComponentConsoleApplication->doRun()
#6 /home/runner/work/server/server/lib/private/Console/Application.php(187): SymfonyComponentConsoleApplication->run()
#7 /home/runner/work/server/server/console.php(91): OCConsoleApplication->run()
#8 /home/runner/work/server/server/occ(33): require_once('...')
#9 {main}

@salmart-dev salmart-dev marked this pull request as ready for review September 10, 2025 14:04
@salmart-dev salmart-dev requested a review from a team as a code owner September 10, 2025 14:04
@salmart-dev salmart-dev requested review from Altahrim and sorbaugh and removed request for a team September 10, 2025 14:04
@salmart-dev salmart-dev changed the title Add INodeByPath to Directory [wip] Add INodeByPath to Directory Sep 10, 2025
Comment on lines +524 to +527
// In case reading a directory was allowed but it turns out the node was a not a directory, reject it now.
if (!$this->info->isReadable()) {
throw new NotFound();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is $this->info used here and not $info?
Also I do not understand the comment, is it related to this if?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@come-nc $this->info refers to the node on which getNodeForPath gets called.

Regarding the comment: it is a leftover of the checks for the files drop checks in getChild which I had dropped for this function. But of course dropping the checks was not OK, since now I can skip ACLs from the files_accesscontrol app. I tested this after discovering the files_accesscontrol app and I am able to do things I shouldn't be able to.

I will add a comment to describe the problem and see if anyone has ideas on what else can break and if there are other solutions other than abandoning the PR.

return $this->node;
}

public function getNodeForPath($path) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function getNodeForPath($path) {
public function getNodeForPath($path): INode {

or whatever is the correct node ;)

@salmart-dev salmart-dev marked this pull request as draft September 15, 2025 16:38
@salmart-dev salmart-dev changed the title Add INodeByPath to Directory [WIP] Add INodeByPath to Directory Sep 15, 2025
@salmart-dev
Copy link
Contributor Author

I converted the PR back to a draft because I ran into an issue with access control rules.

Problem statement

Apps that rely on path navigation to perform checks that block an action could be broken with this PR.

Example

The files_accesscontrol app relies on each node's name to match for names that have been blocked. With this PR, the intermediate nodes in a path may never be navigated to, creating an issue where a file deep in the tree can be accessed, although one of the skipped nodes is marked as not readable through the app.

In the current implementation, the app relies on the fact that isReadable gets called in every Node, which in turn triggers the check for the file name in the app.

How to reproduce

  1. Create a public share
  2. Add a folder inside the public share
  3. Block access to the directory through a flow with the files_accesscontrol app
  4. Try uploading inside the new folder

Uploading should fail but succeeds instead

Possible solution?

Alter files_accesscontrol

For the case in the example, a fix could be to change files_accesscontrol to stop relying on the assumption that all nodes in a path will be navigated to and compare each part of the exploded path with the given value to block, but I fear this would break other flows and checks.


Can someone come up with ideas on how to address this or other things that may break, so that if we try a new solution I can test it still works?

@salmart-dev salmart-dev force-pushed the fix/directoryAsINodeByPath branch from a1a6278 to cf952ce Compare September 16, 2025 08:08
@icewind1991
Copy link
Member

stop relying on the assumption that all nodes in a path will be navigated to and compare each part of the exploded path

This is probably the best option imo.

Another alternative would be to have a dav plugin in files_accesscontrol to do extra checking without effecting any of the other rules or non-dav code paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants