fix(outlook): prefix content document identities so DLS can match them - #4291
Merged
Jan-Kazlouski-elastic merged 5 commits intoJul 31, 2026
Merged
Conversation
Access control documents grant prefixed identities (email:user@x.de) while content documents were decorated with the raw SMTP address, so the DLS terms query never matched and the owner of a mailbox could retrieve none of their own documents. Route the mailbox address through _prefix_email at every call site, and normalise casing inside that helper, since both sides share it and a terms query is case-sensitive. Closes #4290 Co-authored-by: Cursor <cursoragent@cursor.com>
erikcurrin-elastic
approved these changes
Jul 29, 2026
Contributor
Author
E2E confirmationVerified end-to-end against a real Outlook Cloud mailbox + local ES/Kibana (trial license) on this branch:
So the content/ACL identity dialects match and Document Level Security works for the mailbox owner after a full content sync + access control sync. |
Jan-Kazlouski-elastic
deleted the
fix/outlook-dls-identity-prefix-mismatch
branch
July 31, 2026 15:14
This was referenced Jul 31, 2026
💔 Failed to create backport PR(s)
Successful backport PRs will be merged automatically after passing CI. To backport manually run: |
This was referenced Jul 31, 2026
Jan-Kazlouski-elastic
added a commit
that referenced
this pull request
Jul 31, 2026
…tch them (#4291) (#4315) Backports the following commits to 8.19: - fix(outlook): prefix content document identities so DLS can match them (#4291) Made with [Cursor](https://cursor.com) Co-authored-by: Cursor <cursoragent@cursor.com>
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.
Closes #4290
With DLS enabled, the Outlook connector writes its two sides in different dialects. Access control documents grant prefixed identities, while every content document is decorated with the raw SMTP address:
DLS filters
_allow_access_controlwith atermsquery, so the intersection being empty means the owner of a mailbox retrieves none of their own documents. This is pre-existing since DLS was added and affects all five_decorate_with_access_controlcall sites (mails, attachments, contacts, tasks, calendars).This routes the mailbox address through
_prefix_emailat every call site. Casing is normalised inside that helper rather than at the call sites, because both sides of the comparison already share it, so the invariant holds by construction instead of by convention.Two smaller things came along with it, both load-bearing rather than drive-by:
termsquery is case-sensitive, and a mailbox's primary SMTP address is not guaranteed to match the casing the directory reports for the same person. Prefixing alone would leave that as a second way to silently match nothing.Noneidentities. A mailbox with no address prefixes toNone, and_decorate_with_access_controlwould previously store that null in the terms list.es_access_control_queryalready filtersNoneon the access control side; this makes the content side agree.What this does not fix
Access control documents take the address from the directory (Graph's
mailon cloud, the LDAPmailattribute on server), while content documents take it from the mailbox (primary_smtp_address). Those are normally the same address, and after this change they also agree on casing, but they are not guaranteed to be equal: a tenant using aliases or proxy addresses can have a directorymailthat differs from the mailbox's primary SMTP address, and for those users the terms query still will not match. Covering that properly means granting every proxy address a mailbox answers to, which is a larger change and deserves its own issue. Recording it here so it is a known limitation rather than a surprise.Upgrade note
The incorrect values are stored on the content documents themselves, not only in the query template, so deploying this is not sufficient on its own. Affected deployments need a full content sync to rewrite
_allow_access_controlon every document; an access control sync alone will not do it. This differs from #4005, where only the stored query was wrong and re-running the access control sync was enough.Worth knowing alongside that: documents indexed before DLS was switched on carry no
_allow_access_controlfield, and themust_not existsclause inDLS_QUERYmakes those visible to everyone. That is existing framework behaviour rather than anything this PR changes, but it does mean enabling DLS never retroactively protects already-indexed content.Verification
Beyond unit tests, I confirmed the bug and the fix end to end against a real Elasticsearch 8.19.0. Both documents were produced by driving the connector itself rather than written by hand, the index was created with the framework's own mappings, and the mustache template stored on the access control document was rendered by Elasticsearch and then executed.
Before:
email:prefix applied_allow_access_controlAfter, on this branch, the first row is
VISIBLEin both configurations.One thing worth knowing if you reproduce this: the two versions have to be paired correctly or you will get a misleading result.
mainqueries_allow_access_control.keywordand applies no mappings, so dynamic mapping supplies.keyword; 8.19 queries_allow_access_control.enumand appliesMappings.default_text_fields_mappings, whose dynamic template supplies.enum. Each is internally consistent, and both fail for the same reason, which is what makes the prefix the sole cause.Checklists
Pre-Review Checklist
config.yml.example)v7.13.2,v7.14.0,v8.0.0)Tests. Six new tests. The three
test_content_documents_are_reachable_*ones assert the invariant directly — that the identities on content documents intersect the identities the access control document grants — for the cloud path, the server/LDAP path, and mismatched address casing. This is the dual-side assertion the Outlook suite was missing: it asserted the access control side withassert len(acl) == 1and never looked at_allow_access_controlon content documents, which is why this survived. Jira, Confluence and OneDrive already assert both sides.I checked the new tests are load-bearing by reverting only the source change and re-running them:
Full suite on this branch:
2301 passed, coverage 92.19%.Local run.
make autoformat lint test PYTHON=python3.11. Ruff is clean.typecheckreports 13 pyrightunknown import symbolerrors inkibana.py,service_cli.py,agent/,cli/,es/,protocol/andservices/— all pre-existing onmainand none in files this PR touches.Changes Requiring Extra Attention
This changes which documents a user can retrieve under Document Level Security, so it deserves a careful read. The change only ever narrows nothing and widens access from "nobody sees anything" to "the mailbox owner sees their own mailbox" — content documents still carry exactly one identity, the mailbox they came from, which is the same value as before with a prefix applied. No document becomes visible to anyone other than its own mailbox owner.
Worth flagging explicitly: documents with no
_allow_access_controlfield are visible to everyone by design (themust_not existsclause inDLS_QUERY). That is unchanged here, and Outlook always sets the field when DLS is on.Related Pull Requests
Release Note
Fixed Document Level Security for the Outlook connector, where content documents were indexed with identities that did not match the ones granted by the access control documents, so no synced document was retrievable by the user who owned it.