-
-
Notifications
You must be signed in to change notification settings - Fork 640
Migrate MicrosoftTeams plugin from Bot Framework SDK to M365 Agents SDK #1372
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,41 @@ | ||
| using Microsoft.Agents.Builder; | ||
| using Microsoft.Agents.Hosting.AspNetCore; | ||
| using Microsoft.AspNetCore.Authorization; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using Microsoft.Bot.Builder; | ||
| using Microsoft.Bot.Builder.Integration.AspNet.Core; | ||
|
|
||
| namespace BotSharp.Plugin.MicrosoftTeams.Controllers; | ||
|
|
||
| [ApiController] | ||
| public class TeamsMessageController : ControllerBase | ||
| { | ||
| private readonly IBotFrameworkHttpAdapter _adapter; | ||
| private readonly IBot _bot; | ||
| private readonly TeamsRequestState _requestState; | ||
| private readonly IAgentHttpAdapter _adapter; | ||
| private readonly IAgent _bot; | ||
| private readonly ITeamsNotificationService _notification; | ||
| private readonly MicrosoftTeamsSetting _setting; | ||
|
|
||
| public TeamsMessageController( | ||
| IBotFrameworkHttpAdapter adapter, | ||
| IBot bot, | ||
| TeamsRequestState requestState, | ||
| IAgentHttpAdapter adapter, | ||
| IAgent bot, | ||
| ITeamsNotificationService notification, | ||
| MicrosoftTeamsSetting setting) | ||
| { | ||
| _adapter = adapter; | ||
| _bot = bot; | ||
| _requestState = requestState; | ||
| _notification = notification; | ||
| _setting = setting; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Inbound endpoint registered as the Azure Bot "messaging endpoint". | ||
| /// Authentication is performed by the Bot Framework JWT pipeline inside the adapter, | ||
| /// so the action itself is anonymous. | ||
| /// https://learn.microsoft.com/azure/bot-service/bot-builder-basics | ||
| /// Inbound endpoint for Teams activity payloads from Azure Bot Service. | ||
| /// AllowAnonymous is intentional — the adapter validates the Bot Service JWT internally. | ||
| /// Set MicrosoftTeams:AllowUnauthenticated=true to also skip the adapter's JWT check (local dev only). | ||
| /// </summary> | ||
| [AllowAnonymous] | ||
| [HttpPost("/teams/messages/{agentId}")] | ||
| public async Task PostAsync([FromRoute] string agentId) | ||
| public async Task PostAsync([FromRoute] string agentId, CancellationToken cancellationToken) | ||
| { | ||
| _requestState.AgentId = agentId; | ||
| await _adapter.ProcessAsync(Request, Response, _bot); | ||
| Request.Headers.Remove("Authorization"); | ||
| await _adapter.ProcessAsync(Request, Response, _bot, cancellationToken); | ||
| } | ||
|
Comment on lines
34
to
39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Route agentid ignored The inbound Teams message endpoint accepts an {agentId} route value but neither validates nor uses
it, so requests silently default to MicrosoftTeamsSetting.AgentId regardless of the URL. This
makes the route parameter misleading, breaks per-route agent routing, and violates boundary input
validation requirements by masking client/configuration errors.
Agent Prompt
|
||
|
|
||
| /// <summary> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,12 @@ | ||
| using Microsoft.Bot.Builder; | ||
| using Microsoft.Bot.Builder.Integration.AspNet.Core; | ||
| using Microsoft.Bot.Connector.Authentication; | ||
| using Microsoft.Agents.Authentication; | ||
| using Microsoft.Agents.Authentication.Msal; | ||
| using Microsoft.Agents.Builder; | ||
| using Microsoft.Agents.Hosting.AspNetCore; | ||
|
|
||
| namespace BotSharp.Plugin.MicrosoftTeams; | ||
|
|
||
| /// <summary> | ||
| /// Two-way Microsoft Teams integration built on Azure Bot Service / Bot Framework. | ||
| /// Two-way Microsoft Teams integration built on Azure Bot Service / Microsoft 365 Agents SDK. | ||
| /// Inbound: Teams activities are routed into the BotSharp conversation engine. | ||
| /// Outbound: proactive messages are pushed back via stored conversation references. | ||
| /// https://learn.microsoft.com/microsoftteams/platform/bots/what-are-bots | ||
|
|
@@ -23,31 +24,47 @@ public void RegisterDI(IServiceCollection services, IConfiguration config) | |
| config.Bind("MicrosoftTeams", settings); | ||
| services.AddSingleton(settings); | ||
|
|
||
| // Bot Framework authentication. ConfigurationBotFrameworkAuthentication expects the | ||
| // canonical Microsoft* keys, so map our section onto them. | ||
| // Build sub-config with Connections section required by Microsoft.Agents.Authentication.Msal. | ||
| // Maps our MicrosoftTeamsSetting keys to the format ConfigurationConnections expects. | ||
| var authConfig = new ConfigurationBuilder() | ||
| .AddInMemoryCollection(new Dictionary<string, string?> | ||
| { | ||
| ["MicrosoftAppType"] = settings.AppType, | ||
| ["MicrosoftAppId"] = settings.AppId, | ||
| ["MicrosoftAppPassword"] = settings.AppPassword, | ||
| ["MicrosoftAppTenantId"] = settings.TenantId | ||
| ["Connections:BotServiceConnection:Assembly"] = "Microsoft.Agents.Authentication.Msal", | ||
| ["Connections:BotServiceConnection:Type"] = "MsalAuth", | ||
| ["Connections:BotServiceConnection:Settings:AuthType"] = "ClientSecret", | ||
| ["Connections:BotServiceConnection:Settings:ClientId"] = settings.AppId, | ||
| ["Connections:BotServiceConnection:Settings:ClientSecret"] = settings.AppPassword, | ||
| ["Connections:BotServiceConnection:Settings:TenantId"] = settings.TenantId, | ||
| ["Connections:BotServiceConnection:Settings:Scopes:0"] = "https://api.botframework.com/.default", | ||
| }) | ||
|
Comment on lines
+27
to
39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 6. Apptype/msi support dropped MicrosoftTeamsPlugin hardcodes MSAL auth to ClientSecret using settings.AppPassword, while MicrosoftTeamsSetting still documents UserAssignedMSI and an empty secret. Deployments using MSI (or any non-client-secret auth mode) will stop working or misconfigure authentication. Agent Prompt
|
||
| .Build(); | ||
| services.AddSingleton<BotFrameworkAuthentication>(sp => | ||
| new ConfigurationBotFrameworkAuthentication(authConfig)); | ||
|
|
||
| // Adapter (shared by inbound ProcessAsync and proactive ContinueConversationAsync). | ||
| services.AddSingleton<TeamsAdapter>(); | ||
| services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetRequiredService<TeamsAdapter>()); | ||
| // Register MSAL credential provider used by RestChannelServiceClientFactory. | ||
| services.AddDefaultMsalAuth(authConfig); | ||
|
|
||
| // AddDefaultMsalAuth only registers the MSAL factory, not IConnections itself. | ||
| // Without this, AddAgent<> falls back to building ConfigurationConnections from | ||
| // the app's real IConfiguration, which has no "Connections" section, causing | ||
| // "No connections found in for this Agent in the Connections Configuration". | ||
| services.AddSingleton<IConnections>(sp => new ConfigurationConnections(sp, authConfig)); | ||
|
|
||
| // Register adapter + full SDK infrastructure (IChannelServiceClientFactory, IActivityTaskQueue, | ||
| // background services, IAgentHttpAdapter, etc.) and TeamsActivityBot as default IAgent. | ||
| services.AddAgent<TeamsActivityBot, TeamsAdapter>(); | ||
|
|
||
| // Expose adapter as IChannelAdapter so TeamsNotificationService can call ContinueConversationAsync. | ||
| // AddAgent registers the adapter only as IAgentHttpAdapter, so resolve through that interface. | ||
| services.AddSingleton<IChannelAdapter>(sp => (TeamsAdapter)sp.GetRequiredService<IAgentHttpAdapter>()); | ||
|
|
||
| services.AddSingleton<AdaptiveCardConverter>(); | ||
| services.AddSingleton<IConversationReferenceStore, InMemoryConversationReferenceStore>(); | ||
| services.AddSingleton<ITeamsNotificationService, TeamsNotificationService>(); | ||
|
|
||
| // Per-request / per-turn services. | ||
| services.AddScoped<TeamsRequestState>(); | ||
| // Per-turn services. | ||
| services.AddScoped<TeamsMessageHandler>(); | ||
| services.AddTransient<IBot, TeamsActivityBot>(); | ||
|
|
||
| // Override IAgent lifetime to transient so each turn gets a fresh instance and | ||
| // scoped dependencies (TeamsRequestState, TeamsMessageHandler) resolve correctly. | ||
| services.AddTransient<IAgent, TeamsActivityBot>(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4. Authorization header stripped
🐞 Bug⛨ SecurityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools