Skip to content

Comments

Permissions: Fix GetPermissionsAsync to resolve permissions from nearest ancestor#21741

Merged
lauraneto merged 7 commits intomainfrom
v17/bugfix/fix-tree-permissions-filtering
Feb 16, 2026
Merged

Permissions: Fix GetPermissionsAsync to resolve permissions from nearest ancestor#21741
lauraneto merged 7 commits intomainfrom
v17/bugfix/fix-tree-permissions-filtering

Conversation

@lauraneto
Copy link
Contributor

@lauraneto lauraneto commented Feb 12, 2026

Summary

  • GetPermissionsAsync was querying only explicit per-node permissions, ignoring ancestor-based permission resolution. Nodes without explicit permissions would get group defaults instead of inheriting from their nearest ancestor with explicit permissions. This caused tree filtering to hide child nodes that should have been visible (e.g. documents created in a folder were not appearing in the tree).
  • Replace per-node permission queries with GetPermissionsForPath which walks the entity path to resolve permissions from the nearest ancestor. Also pass object types through to enable batched entity lookups via GetAll.
  • Add 5 integration tests covering child inheritance, grandchild inheritance, explicit permission override, nearest-ancestor resolution, and unknown user handling.

Testing

To reproduce the original issue:

  1. Create a "Parent" document with a child document underneath it
  2. Create a non-admin user group with access to the Content section, Root as start node, no default document permissions, and set granular permissions: Read on the Parent
  3. Log in as a user in that group
  4. Navigate to the Content section and expand the Parent document

The Parent's children endpoint would return 0 results, making the child document invisible in the tree. But if you try to access the child's url directly you can view it.
After this fix, child documents correctly resolve permissions from their nearest ancestor and appear as expected.

GetPermissionsAsync was querying only explicit per-node permissions,
ignoring the ancestor-based inheritance model. Nodes without explicit
permissions would get group defaults instead of inheriting from their
nearest ancestor with explicit permissions. This caused tree filtering
to hide child nodes that should have been visible.

Replace per-node permission queries with GetPermissionsForPath which
walks the entity path to resolve inherited permissions correctly. Also
pass object types through to enable batched entity lookups.
@lauraneto lauraneto marked this pull request as ready for review February 12, 2026 15:21
Copilot AI review requested due to automatic review settings February 12, 2026 15:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes permission resolution for document/media nodes so permissions are inherited from the nearest ancestor with explicit permissions (instead of falling back to group defaults when the node itself has no explicit permissions). This aligns permission checks with how the content tree should be filtered/visible in the backoffice.

Changes:

  • Update UserService document/media permission resolution to use path-based permission inheritance via GetPermissionsForPath.
  • Add integration tests validating inheritance/override behavior and unknown-user handling.
  • Split UserServiceTests into partial files and wire the new test file into the integration test project.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj Includes the new partial test file under UserServiceTests for compilation/IDE nesting.
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs Marks the fixture as partial to support splitting tests across files.
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.Permissions.cs Adds 5 integration tests covering inherited permissions, override behavior, nearest-ancestor selection, and unknown user.
src/Umbraco.Core/Services/UserService.cs Switches document/media permission resolution to path-based inheritance using entity paths.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

@AndyButland AndyButland left a comment

Choose a reason for hiding this comment

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

This fix looks good to me @lauraneto, nice catch. I've tested it out with the steps you describe and can see the original problem and confirm that this resolves it. The integration tests provide good and sufficient coverage too.

I did want to have a look at the performance concern raised in the Copilot review, as I've been doing a bit of work recently looking to avoid these types of "N+1" queries (see #21684). Of course correct and a bit slower is better than fast and wrong! But I think there's a way to do similar here, so have done so.

I made a benchmark test that seems like it's realistic, and got some good numbers:

Before:
GetDocumentPermissionsAsync_Performance_50_SiblingsGetDocumentPermissionsAsync — 50 children, 5 iterations:
median=86ms, min=74ms, max=93ms, all=[74ms, 92ms, 93ms, 86ms, 84ms]

After:
GetDocumentPermissionsAsync_Performance_50_SiblingsGetDocumentPermissionsAsync — 50 children, 5 iterations:
median=8ms, min=7ms, max=10ms, all=[7ms, 10ms, 9ms, 7ms, 8ms]

The integration tests continues to pass and the manual check looks OK, so I think it's good. But perhaps you could have a look over to see if you see any concerns and agree with the approach before we merge this.

@lauraneto
Copy link
Contributor Author

@AndyButland Thank you for checking, and amazing work on the performance improvements!
While checking I realized we were missing two additional test cases that I wanted to ensure were working correctly, so I added:

  • GetDocumentPermissionsAsync_Returns_Default_Permissions_When_No_Explicit_Permissions_Set
  • GetDocumentPermissionsAsync_Child_Permissions_Do_Not_Influence_Parent

@AndyButland
Copy link
Contributor

Great, those two additional tests are useful to have. Feel free to merge this in if you are happy with it, it all looks good to me.

@lauraneto lauraneto merged commit 9c1a810 into main Feb 16, 2026
25 of 26 checks passed
@lauraneto lauraneto deleted the v17/bugfix/fix-tree-permissions-filtering branch February 16, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants