feat(apps): resolve Agentic User file bytes through Graph /shares - #669
Open
Corina (corinagum) wants to merge 20 commits into
Open
feat(apps): resolve Agentic User file bytes through Graph /shares#669Corina (corinagum) wants to merge 20 commits into
Corina (corinagum) wants to merge 20 commits into
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved findings include FileDownloader binary compatibility and HTTPS validation for Graph endpoints.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds Microsoft Graph /shares resolution for Agentic User attachments lacking downloadUrl, while preserving pre-authorized URL handling and terminal expiry behavior.
Changes:
- Adds Graph credentials, routing, host configuration, and attachment mapping.
- Updates file downloading, error handling, and application wiring.
- Adds unit/integration coverage and sample documentation.
File summaries
| File | Summary |
|---|---|
test/Microsoft.Teams.Core.UnitTests/Hosting/BotTokenProviderAgenticIdentityTests.cs |
Tests Agentic identity token behavior. |
test/Microsoft.Teams.Core.UnitTests/Hosting/BotConfigTests.cs |
Tests Graph host configuration. |
test/Microsoft.Teams.Apps.UnitTests/TeamsBotApplicationTests.cs |
Tests constructor compatibility. |
test/Microsoft.Teams.Apps.UnitTests/TeamsActivityTests.cs |
Tests tenant resolution. |
test/Microsoft.Teams.Apps.UnitTests/FilesCredentialTests.cs |
Tests credential selection. |
test/Microsoft.Teams.Apps.UnitTests/Files/IncomingFileTests.cs |
Tests file response handling and cancellation. |
test/Microsoft.Teams.Apps.UnitTests/Files/GraphShareTests.cs |
Tests Graph share URL encoding. |
test/Microsoft.Teams.Apps.UnitTests/Files/FilesAccessorAgenticTests.cs |
Tests content-only attachment mapping. |
test/Microsoft.Teams.Apps.UnitTests/Files/FileErrorsTests.cs |
Tests file error contracts. |
test/Microsoft.Teams.Apps.UnitTests/Files/FileDownloaderTransportTests.cs |
Tests transport authentication behavior. |
test/Microsoft.Teams.Apps.UnitTests/Files/FileDownloaderGraphTests.cs |
Tests Graph downloads and failures. |
test/Microsoft.Teams.Apps.UnitTests/CustomBotApplicationGraphWiringTests.cs |
Tests custom application Graph wiring. |
test/Microsoft.Teams.Apps.UnitTests/ContextFilesCredentialTests.cs |
Tests context credential wiring. |
test/IntegrationTests/GraphFilesFixture.cs |
Provides Graph integration test data. |
test/IntegrationTests/FilesIntegrationTests.cs |
Updates file integration coverage. |
test/IntegrationTests/FilesGraphIntegrationTests.cs |
Tests live Graph retrieval and expiry. |
src/Microsoft.Teams.Core/Microsoft.Teams.Core.csproj |
Grants integration-test internals access. |
src/Microsoft.Teams.Core/Hosting/BotTokenProvider.cs |
Adds app and Agentic Graph token acquisition. |
src/Microsoft.Teams.Core/Hosting/BotConfig.cs |
Adds Graph host configuration. |
src/Microsoft.Teams.Core/Hosting/BotAuthenticationHandler.cs |
Shares token option construction. |
src/Microsoft.Teams.Core/Hosting/AddBotApplicationExtensions.cs |
Registers token provider services. |
src/Microsoft.Teams.Core/GlobalSuppressions.cs |
Suppresses Graph URI configuration warnings. |
src/Microsoft.Teams.Apps/TeamsBotApplicationOptions.cs |
Adds Graph host options. |
src/Microsoft.Teams.Apps/TeamsBotApplication.HostingExtensions.cs |
Wires Graph services and configuration. |
src/Microsoft.Teams.Apps/TeamsBotApplication.cs |
Adds Graph token and downloader wiring. |
src/Microsoft.Teams.Apps/Schema/TeamsActivity.cs |
Adds tenant fallback resolution. |
src/Microsoft.Teams.Apps/FilesCredential.cs |
Selects file credentials. |
src/Microsoft.Teams.Apps/Files/IncomingFile.cs |
Carries Graph locators and credentials. |
src/Microsoft.Teams.Apps/Files/GraphShare.cs |
Builds Graph share URLs. |
src/Microsoft.Teams.Apps/Files/GraphCredential.cs |
Defines Graph credential data. |
src/Microsoft.Teams.Apps/Files/FilesAccessor.cs |
Maps content-only attachments. |
src/Microsoft.Teams.Apps/Files/FileErrors.cs |
Adds Graph retrieval errors. |
src/Microsoft.Teams.Apps/Files/FileDownloadInfo.cs |
Updates file locator metadata. |
src/Microsoft.Teams.Apps/Files/FileDownloader.cs |
Implements pre-authorized and Graph download routes. |
src/Microsoft.Teams.Apps/Context.cs |
Connects file access to credentials. |
samples/AIFileAnalysisBot/README.md |
Documents file retrieval routes. |
samples/AIFileAnalysisBot/FileCard.cs |
Clarifies the displayed file source. |
Review details
Suppressed comments (4)
samples/AIFileAnalysisBot/README.md:83
- This rule is unconditional, but
FilesAccessoronly admits a content-URL-only attachment forpersonalscope and skipsgroupChat/channelattachments until those receive paths exist. State that scope restriction here so users do not expect a file handle, or a Graph fetch, from those activities.
The route is determined by whether or not the pre-authorized URL exists. If it does, it is used. Otherwise, the Graph path is followed.
samples/AIFileAnalysisBot/README.md:80
- The new sentence is missing the article "a"; it should read "Teams provides a URL on the attachment."
- **A bot reads through the pre-authorized download URL.** Teams provides URL on the attachment and the SDK fetches it directly, with no tokens attached, shown in this example.
src/Microsoft.Teams.Apps/Files/FileErrors.cs:233
FileActor.Appis a real Graph route here: content-URL-only files select this actor andGetAppGraphTokenAsyncsupplies an app token, and the Graph tests cover application roles. This message therefore tells ordinary bots that an app credential/permission can never fix the failure, which contradicts the implemented path and sends users to the wrong remedy. Update the message and its assertion to describe missing app Graph credentials/permissions and file access.
FileActor.App => "the app has no usable Graph credential for this file. Graph file retrieval is supported for Agentic Users, which read as their own identity; an app identity and/or user-delegated permissions may be used but are not supported via the SDK at this time",
test/Microsoft.Teams.Apps.UnitTests/Files/IncomingFileTests.cs:129
- The test handler ignores
cancellationTokenand always returns a response. The assertion can still pass becauseDownloadAsynclater receives the same canceled token during content copy, so this test would stay green if a refactor passedCancellationToken.Noneto the transport. Record the token or make the handler fail based on the token it receives to actually pin propagation throughSendAsync.
- Files reviewed: 37/37 changed files
- Comments generated: 6
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Port of the TypeScript change. FileRetrievalException and FileRetrievalFailureReason are replaced by FileCredentialException and FileAccessException, and the actor is no longer defaulted. Two deliberate divergences. There is no tenant threading, because this provider applies no tenant override by design. The redirect guard is post-hoc on the final hop rather than per-hop, matching the existing 'must use https' InvalidOperationException precedent in this repo. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7d326e8a-aeb4-4481-bc0a-58d2a012584e
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.
What this does
Agentic User file attachments arrive with a
contentUrland nodownloadUrl, so the pre-authorized ("tempauth") URL traditional bots rely on is absent. This resolves those bytes through Microsoft Graph,GET /shares/{u!encoded}/driveItem/content, using the Agentic User's own credential.The route is chosen from the shape of the attachment, not from the identity: a
contentUrlwith nodownloadUrl, at personal scope, resolves viagraphShare. Everything else keeps the existingpreauthUrlpath unchanged.What it deliberately does not do
An expired pre-authorized URL is terminal. It raises
FileUrlExpiredErrorand makes no Graph call.A developer can still call Graph with their own client, and keeps the locator because
contentUrlis public API. The constraint is on what the SDK does automatically.Auditing trap: the agentic token is itself delegated-shaped, carrying
scprather thanroles. Follow the identity, not the word "delegated".Why
.defaultand not a named scopeA by-name request hard-fails on exact-string consent mismatch: a blueprint consented to
Files.ReadWrite.AllreturnsAADSTS65001and no token at all for aFiles.Read.Allrequest. Graph's own/sharesdocs listFiles.ReadWrite.Allas least-privileged and list no read-only permission, so admins following Microsoft's guidance land exactly where a by-name request breaks.The cost is a late, ambiguous 403 when a file permission is missing. We pay that in the SDK instead: the pre-flight rejects a token carrying permissions but none file-capable, which restores an early, legible failure. A
Files.Read.All-then-.defaultladder was rejected, becauseAADSTS65001is also what "no file permission consented at all" produces, so the ladder makes the most common deployment error harder to diagnose, not easier.For security review: under
.defaultthe token carries the blueprint's full consented set, including unrelated scopes. The mitigation is the grant, not the request string: do not consent unrelated scopes on a file-handling blueprint.Testing
Unit and integration coverage is in the diff. All three scenarios were also run live against real Teams, tenant asserted twice per run (browser session state, and the SharePoint host in the bot's own log).
preauthUrl, byte-exact/shares/{u!}/driveItem/content200, byte-exact, driving the shipped helpersFileUrlExpiredError, zero requests tograph.microsoft.comin the entire process logThe third row was checked by grepping the whole bot process log for an absence, not by reading the rendered result, and the expiry is real rather than simulated: the same
UniqueIdreturns 200 early and a hard 401 later. And the pre-authorized URL lifetime is not a constant: three runs bounded the flip at (62.7, 72.4], (62.5, 68.4] and (63.6, 66.4] minutes. Poll rather than picking a wait time.This language
13 commits, 37 files, +3161 / -66.
A compatibility overload is kept deliberately. C# bakes optional arguments into the caller, so removing the five-parameter
TeamsBotApplicationsignature is a runtimeMissingMethodExceptionfor anything compiled against the old shape, even though the source change is compatible. Verified against the realv2.1.0tag: that constructor exists there, and it is the only at-risk shipped signature in the change. It is hidden from IntelliSense and pinned by a reflection assertion against emitted metadata, because a positional call would simply rebind to the six-parameter overload and prove nothing.ExpiredPreauthUrl_IsTerminal_AndDoesNotReachForGraphdrives the shipped downloader with an expireddownloadUrland a validcontentUrl, the locator a fallback would use, and asserts zero token acquisitions. Supplying thecontentUrlis the point: the URL is terminal because there is no recovery, not because the test withheld the means to recover.Microsoft.Teams.Graphdoes not exist and is not this PR's job. .NET uses the official Graph package.Running the integration tests.
test/IntegrationTestsis deliberately not referenced byMicrosoft.Teams.slnx, so solution-wide runs skip it. Invoke it by project path.