-
Notifications
You must be signed in to change notification settings - Fork 1.7k
.NET: Support ClaimsIdentity-based scoping of agent sessions #5696
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
lokitoth
wants to merge
8
commits into
main
Choose a base branch
from
dev/claims_identity_scoped_session_store
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
44aec56
feat: Add DelegatingAgentSessionStore
lokitoth f798c0b
feat: Add UserIdentityScopedSessionStore
lokitoth c36fe88
fix: Harden scope mapping
lokitoth 0c1306e
fix: Add UserIdentityScopeSessionStoreOptions to avoid future breakin…
lokitoth 70f47ed
Merge branch 'main' into dev/claims_identity_scoped_session_store
lokitoth 7a19e2f
Merge branch 'main' into dev/claims_identity_scoped_session_store
lokitoth 921d9f0
Merge branch 'main' into dev/claims_identity_scoped_session_store
lokitoth 9429094
Merge branch 'main' into dev/claims_identity_scoped_session_store
lokitoth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
30 changes: 30 additions & 0 deletions
30
.../src/Microsoft.Agents.AI.Hosting.AspNetCore/Microsoft.Agents.AI.Hosting.AspNetCore.csproj
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks> | ||
| <RootNamespace>Microsoft.Agents.AI.Hosting.AspNetCore</RootNamespace> | ||
| <VersionSuffix>preview</VersionSuffix> | ||
| <NoWarn>$(NoWarn)</NoWarn> | ||
| </PropertyGroup> | ||
|
|
||
| <Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" /> | ||
|
|
||
| <PropertyGroup> | ||
| <InjectSharedThrow>true</InjectSharedThrow> | ||
| <InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds> | ||
| <InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\Microsoft.Agents.AI.Hosting\Microsoft.Agents.AI.Hosting.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <PropertyGroup> | ||
| <!-- NuGet Package Settings --> | ||
| <Title>Microsoft Agent Framework Hosting ASP.NET Core</Title> | ||
| <Description>Provides Microsoft Agent Framework support for hosting agents in an ASP.NET Core context.</Description> | ||
| </PropertyGroup> | ||
| </Project> |
84 changes: 84 additions & 0 deletions
84
dotnet/src/Microsoft.Agents.AI.Hosting.AspNetCore/UserIdentityScopedSessionStore.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System; | ||
| using System.Linq; | ||
| using System.Security.Claims; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.AspNetCore.Http; | ||
| using Microsoft.Shared.Diagnostics; | ||
|
|
||
| namespace Microsoft.Agents.AI.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// A delegating <see cref="AgentSessionStore"/> that scopes session keys by a claim value | ||
| /// extracted from the current user's identity, ensuring that sessions are isolated per user. | ||
| /// The current user is extracted from the ambient ASP.NET <see cref="HttpContext"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This relies on <see cref="IHttpContextAccessor"/>, which uses <see cref="AsyncLocal{T}"/> | ||
| /// to provide access to the current <see cref="HttpContext"/>. | ||
| /// </remarks> | ||
| public class UserIdentityScopedSessionStore : DelegatingAgentSessionStore | ||
| { | ||
| private readonly IHttpContextAccessor? _httpContextAccessor; | ||
| private readonly string _claimType; | ||
| private readonly bool _strict; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="UserIdentityScopedSessionStore"/> class. | ||
| /// </summary> | ||
| /// <param name="innerStore">The underlying <see cref="AgentSessionStore"/> to delegate to.</param> | ||
| /// <param name="contextAccessor"> | ||
| /// The <see cref="IHttpContextAccessor"/> used to retrieve the current user's claims. | ||
| /// </param> | ||
| /// <param name="options">The options for configuring the session store. If null, defaults are used.</param> | ||
| public UserIdentityScopedSessionStore( | ||
| AgentSessionStore innerStore, | ||
| IHttpContextAccessor? contextAccessor, | ||
| UserIdentityScopedSessionStoreOptions? options = null) : base(innerStore) | ||
| { | ||
| options ??= new UserIdentityScopedSessionStoreOptions(); | ||
|
|
||
| this._httpContextAccessor = contextAccessor; | ||
| this._claimType = Throw.IfNullOrWhitespace(options.ClaimType); | ||
| this._strict = options.Strict; | ||
| } | ||
|
|
||
| private string? GetScopeFromIdentity() | ||
| { | ||
| Claim? claim = this._httpContextAccessor? | ||
| .HttpContext? | ||
| .User?.Claims.FirstOrDefault(c => c.Type == this._claimType); | ||
|
|
||
| if (this._strict && claim == null) | ||
| { | ||
| throw new InvalidOperationException($"No claim of type '{this._claimType}' found in principal."); | ||
| } | ||
|
|
||
| return claim?.Value; | ||
| } | ||
|
|
||
| private string? ScopeId => this.GetScopeFromIdentity(); | ||
|
|
||
| private static string EscapeScopeId(string scopeId) => scopeId.Replace("\\", "\\\\").Replace(":", "\\:"); | ||
|
|
||
| private string GetScopedConversationId(string bareConversationId) | ||
| { | ||
| string? scopeId = this.ScopeId; | ||
| if (scopeId == null) | ||
| { | ||
| return bareConversationId; | ||
| } | ||
|
|
||
| return $"{EscapeScopeId(scopeId)}::{bareConversationId}"; | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public override ValueTask<AgentSession> GetSessionAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default) | ||
| => this.InnerStore.GetSessionAsync(agent, this.GetScopedConversationId(conversationId), cancellationToken); | ||
|
|
||
| /// <inheritdoc /> | ||
| public override ValueTask SaveSessionAsync(AIAgent agent, string conversationId, AgentSession session, CancellationToken cancellationToken = default) | ||
| => this.InnerStore.SaveSessionAsync(agent, this.GetScopedConversationId(conversationId), session, cancellationToken); | ||
| } | ||
29 changes: 29 additions & 0 deletions
29
dotnet/src/Microsoft.Agents.AI.Hosting.AspNetCore/UserIdentityScopedSessionStoreOptions.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System.Security.Claims; | ||
|
|
||
| namespace Microsoft.Agents.AI.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Options for configuring <see cref="UserIdentityScopedSessionStore"/>. | ||
| /// </summary> | ||
| public class UserIdentityScopedSessionStoreOptions | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the claim type to extract from the user's identity for scoping. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Defaults to <see cref="ClaimsIdentity.DefaultNameClaimType"/>. | ||
| /// </remarks> | ||
| public string ClaimType { get; set; } = ClaimsIdentity.DefaultNameClaimType; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a value indicating whether an exception should be thrown when the specified claim is not found. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// If <see langword="true"/>, an exception is thrown when the specified claim is not found. | ||
| /// If <see langword="false"/>, the conversation ID is passed through unmodified when the claim is absent. | ||
| /// Defaults to <see langword="true"/>. | ||
| /// </remarks> | ||
| public bool Strict { get; set; } = true; | ||
| } |
62 changes: 62 additions & 0 deletions
62
dotnet/src/Microsoft.Agents.AI.Hosting/DelegatingAgentSessionStore.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Shared.Diagnostics; | ||
|
|
||
| namespace Microsoft.Agents.AI.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Provides an abstract base class for agent session stores that delegate operations to an inner store | ||
| /// instance while allowing for extensibility and customization. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// <see cref="DelegatingAgentSessionStore"/> implements the decorator pattern for <see cref="AgentSessionStore"/>s, | ||
| /// enabling the creation of pipelines where each layer can add functionality while delegating core operations to an | ||
| /// underlying store. | ||
| /// </para> | ||
| /// <para> | ||
| /// The default implementation provides transparent pass-through behavior, forwarding all operations to the inner store. | ||
| /// Derived classes can override specific methods to add custom behavior while maintaining compatibility with the store | ||
| /// interface. | ||
| /// </para> | ||
| /// </remarks> | ||
| public abstract class DelegatingAgentSessionStore : AgentSessionStore | ||
| { | ||
|
lokitoth marked this conversation as resolved.
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="DelegatingAgentSessionStore"/> class with the specified inner | ||
| /// store. | ||
| /// </summary> | ||
| /// <param name="innerStore">The underlying session store instance that will handle the core operations.</param> | ||
| /// <exception cref="ArgumentNullException"><paramref name="innerStore"/> is <see langword="null"/>.</exception> | ||
| /// <remarks> | ||
| /// The inner session store serves as the foundation of the delegation chain. All operations not overridden by | ||
| /// derived classes will be forwarded to this store. | ||
| /// </remarks> | ||
| protected DelegatingAgentSessionStore(AgentSessionStore innerStore) | ||
| { | ||
| this.InnerStore = Throw.IfNull(innerStore); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the inner session store instance that receives delegated operations. | ||
| /// </summary> | ||
| /// <value> | ||
| /// The underlying <see cref="AgentSessionStore"/> instance that handles core storage operations. | ||
| /// </value> | ||
| /// <remarks> | ||
| /// Derived classes can use this property to access the inner session store for custom delegation scenarios | ||
| /// or to forward operations with additional processing. | ||
| /// </remarks> | ||
| protected AgentSessionStore InnerStore { get; } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override ValueTask<AgentSession> GetSessionAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default) | ||
| => this.InnerStore.GetSessionAsync(agent, conversationId, cancellationToken); | ||
|
|
||
| /// <inheritdoc/> | ||
| public override ValueTask SaveSessionAsync(AIAgent agent, string conversationId, AgentSession session, CancellationToken cancellationToken = default) | ||
| => this.InnerStore.SaveSessionAsync(agent, conversationId, session, cancellationToken); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.