diff --git a/src/Plugins/BotSharp.Plugin.MicrosoftTeams/MicrosoftTeamsPlugin.cs b/src/Plugins/BotSharp.Plugin.MicrosoftTeams/MicrosoftTeamsPlugin.cs index c3b830822..49fada9d8 100644 --- a/src/Plugins/BotSharp.Plugin.MicrosoftTeams/MicrosoftTeamsPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.MicrosoftTeams/MicrosoftTeamsPlugin.cs @@ -1,7 +1,9 @@ using Microsoft.Agents.Authentication; using Microsoft.Agents.Authentication.Msal; using Microsoft.Agents.Builder; +using Microsoft.Agents.Connector; using Microsoft.Agents.Hosting.AspNetCore; +using System.Security.Claims; namespace BotSharp.Plugin.MicrosoftTeams; @@ -48,6 +50,17 @@ public void RegisterDI(IServiceCollection services, IConfiguration config) // "No connections found in for this Agent in the Connections Configuration". services.AddSingleton(sp => new ConfigurationConnections(sp, authConfig)); +#if DEBUG + services.AddSingleton(sp => + { + var inner = new RestChannelServiceClientFactory( + config, + sp.GetRequiredService(), + sp.GetRequiredService()); + return new AnonymousDebugChannelServiceClientFactory(inner); + }); +#endif + // Register adapter + full SDK infrastructure (IChannelServiceClientFactory, IActivityTaskQueue, // background services, IAgentHttpAdapter, etc.) and TeamsActivityBot as default IAgent. services.AddAgent(); @@ -68,3 +81,22 @@ public void RegisterDI(IServiceCollection services, IConfiguration config) services.AddTransient(); } } + +#if DEBUG +file class AnonymousDebugChannelServiceClientFactory(RestChannelServiceClientFactory inner) : IChannelServiceClientFactory +{ + [Obsolete] + public Task CreateConnectorClientAsync(ClaimsIdentity claimsIdentity, string serviceUrl, string audience, CancellationToken cancellationToken, IList? scopes = null, bool useAnonymous = false) + => inner.CreateConnectorClientAsync(claimsIdentity, serviceUrl, audience, cancellationToken, scopes, true); + + // NOTE: inner's ITurnContext overload still branches on Activity.Recipient.Role=="agenticUser" + // regardless of useAnonymous, so route through the ClaimsIdentity overload to actually skip it. + public Task CreateConnectorClientAsync(ITurnContext turnContext, string? audience = null, IList? scopes = null, bool useAnonymous = false, CancellationToken cancellationToken = default) +#pragma warning disable CS0618 // intentionally using the legacy overload to bypass the agentic branch + => inner.CreateConnectorClientAsync(turnContext.Identity, turnContext.Activity.ServiceUrl, audience, cancellationToken, scopes, true); +#pragma warning restore CS0618 + + public Task CreateUserTokenClientAsync(ClaimsIdentity claimsIdentity, bool? useAnonymous = false, CancellationToken cancellationToken = default) + => inner.CreateUserTokenClientAsync(claimsIdentity, true, cancellationToken); +} +#endif \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.MicrosoftTeams/Services/TeamsActivityBot.cs b/src/Plugins/BotSharp.Plugin.MicrosoftTeams/Services/TeamsActivityBot.cs index 64aaad889..49900ed4c 100644 --- a/src/Plugins/BotSharp.Plugin.MicrosoftTeams/Services/TeamsActivityBot.cs +++ b/src/Plugins/BotSharp.Plugin.MicrosoftTeams/Services/TeamsActivityBot.cs @@ -51,6 +51,8 @@ public TeamsActivityBot( public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { + _httpContextAccessor.HttpContext ??= new DefaultHttpContext(); + var aadId = GetUserId(turnContext.Activity); if (!string.IsNullOrEmpty(aadId)) { @@ -250,7 +252,8 @@ private async Task SendWelcomeAsync(ITurnContext turnContext, CancellationToken /// /// Populates HttpContext.User with the BotSharp user's claims so that IUserIdentity - /// resolves correctly for all downstream scoped services during this turn. + /// resolves correctly for all downstream scoped services during this turn. The HttpContext + /// is the turn-owned one created in (the SDK provides none). /// private void SetHttpContextUser(User user) { @@ -264,7 +267,7 @@ private void SetHttpContextUser(User user) new Claim(ClaimTypes.Role, user.Role ?? string.Empty), }; var identity = new ClaimsIdentity(claims, authenticationType: "Teams"); - _httpContextAccessor.HttpContext!.User = new System.Security.Principal.GenericPrincipal(identity, roles: null); + _httpContextAccessor.HttpContext!.User = new ClaimsPrincipal(identity); } private async Task FindUserByEmailAsync(string email, CancellationToken cancellationToken)