microsoft/teams.net

Public

mirrored from https://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/move-activity-implementations

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

core/src/Microsoft.Bot.Core/Hosting/AddBotApplicationExtensions.cs

269lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using Microsoft.AspNetCore.Builder;
5using Microsoft.AspNetCore.Http;
6using Microsoft.Bot.Core.Schema;
7using Microsoft.Extensions.Configuration;
8using Microsoft.Extensions.DependencyInjection;
9using Microsoft.Extensions.Logging;
10using Microsoft.Extensions.Options;
11using Microsoft.Identity.Abstractions;
12using Microsoft.Identity.Web;
13using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
14
15namespace Microsoft.Bot.Core.Hosting;
16
17/// <summary>
18/// Provides extension methods for registering bot application clients and related authentication services with the
19/// dependency injection container.
20/// </summary>
21/// <remarks>This class is intended to be used during application startup to configure HTTP clients, token
22/// acquisition, and agent identity services required for bot-to-bot communication. The configuration section specified
23/// by the Azure Active Directory (AAD) configuration name is used to bind authentication options. Typically, these
24/// methods are called in the application's service configuration pipeline.</remarks>
25public static class AddBotApplicationExtensions
26{
27 /// <summary>
28 /// Configures the application to handle bot messages at the specified route and returns the registered bot
29 /// application instance.
30 /// </summary>
31 /// <remarks>This method adds authentication and authorization middleware to the request pipeline and maps
32 /// a POST endpoint for bot messages. The endpoint requires authorization. Ensure that the bot application is
33 /// registered in the service container before calling this method.</remarks>
34 /// <typeparam name="TApp">The type of the bot application to use. Must inherit from BotApplication.</typeparam>
35 /// <param name="builder">The application builder used to configure the request pipeline.</param>
36 /// <param name="routePath">The route path at which to listen for incoming bot messages. Defaults to "api/messages".</param>
37 /// <returns>The registered bot application instance of type TApp.</returns>
38 /// <exception cref="ApplicationException">Thrown if the bot application of type TApp is not registered in the application's service container.</exception>
39 public static TApp UseBotApplication<TApp>(
40 this IApplicationBuilder builder,
41 string routePath = "api/messages")
42 where TApp : BotApplication
43 {
44 ArgumentNullException.ThrowIfNull(builder);
45 TApp app = builder.ApplicationServices.GetService<TApp>() ?? throw new InvalidOperationException("Application not registered");
46 WebApplication? webApp = builder as WebApplication;
47 ArgumentNullException.ThrowIfNull(webApp);
48 webApp.MapPost(routePath, async (HttpContext httpContext, CancellationToken cancellationToken) =>
49 {
50 CoreActivity resp = await app.ProcessAsync(httpContext, cancellationToken).ConfigureAwait(false);
51 return resp.Id;
52 });
53
54 return app;
55 }
56
57 /// <summary>
58 /// Adds a bot application to the service collection.
59 /// </summary>
60 /// <typeparam name="TApp"></typeparam>
61 /// <param name="services"></param>
62 /// <param name="sectionName"></param>
63 /// <returns></returns>
64 public static IServiceCollection AddBotApplication<TApp>(this IServiceCollection services, string sectionName = "AzureAd") where TApp : BotApplication
65 {
66 services.AddConversationClient(sectionName);
67 services.AddSingleton<TApp>();
68 return services;
69 }
70
71 /// <summary>
72 /// Adds a conversation client to the service collection.
73 /// </summary>
74 /// <param name="services">service collection</param>
75 /// <param name="sectionName">Configuration Section name, defaults to AzureAD</param>
76 /// <returns></returns>
77 public static IServiceCollection AddConversationClient(this IServiceCollection services, string sectionName = "AzureAd")
78 {
79 var sp = services.BuildServiceProvider();
80 IConfiguration configuration = sp.GetRequiredService<IConfiguration>();
81 ILogger logger = sp.GetRequiredService<ILogger<ConversationClient>>();
82 ArgumentNullException.ThrowIfNull(configuration);
83
84 string scope = "https://api.botframework.com/.default";
85 if (configuration[$"{sectionName}:Scope"] is not null)
86 {
87 scope = configuration[$"{sectionName}:Scope"]!;
88 }
89
90 if (configuration["Scope"] is not null) //ToChannelFromBotOAuthScope
91 {
92 scope = configuration["Scope"]!;
93 }
94
95 services
96 .AddHttpClient()
97 .AddTokenAcquisition(true)
98 .AddInMemoryTokenCaches()
99 .AddAgentIdentities();
100
101 if (services.ConfigureMSAL(configuration, sectionName))
102 {
103
104 services.AddHttpClient<ConversationClient>(ConversationClient.ConversationHttpClientName)
105 .AddHttpMessageHandler(sp => new BotAuthenticationHandler(
106 sp.GetRequiredService<IAuthorizationHeaderProvider>(),
107 sp.GetRequiredService<ILogger<BotAuthenticationHandler>>(),
108 scope,
109 sp.GetService<IOptions<ManagedIdentityOptions>>()));
110 }
111 else
112 {
113 _logAuthConfigNotFound(logger, null);
114 services.AddHttpClient<ConversationClient>(ConversationClient.ConversationHttpClientName);
115 }
116 return services;
117 }
118
119 private static bool ConfigureMSAL(this IServiceCollection services, IConfiguration configuration, string sectionName)
120 {
121 ArgumentNullException.ThrowIfNull(configuration);
122 var logger = services.BuildServiceProvider().GetRequiredService<ILoggerFactory>().CreateLogger(typeof(AddBotApplicationExtensions));
123
124 if (configuration["MicrosoftAppId"] is not null)
125 {
126 _logUsingBFConfig(logger, null);
127 var botConfig = BotConfig.FromBFConfig(configuration);
128 services.ConfigureMSALFromBotConfig(botConfig, logger);
129 }
130 else if (configuration["CLIENT_ID"] is not null)
131 {
132 _logUsingCoreConfig(logger, null);
133 var botConfig = BotConfig.FromCoreConfig(configuration);
134 services.ConfigureMSALFromBotConfig(botConfig, logger);
135 }
136 else
137 {
138 _logUsingSectionConfig(logger, sectionName, null);
139 services.ConfigureMSALFromConfig(configuration.GetSection(sectionName));
140 }
141 return true;
142 }
143
144 private static IServiceCollection ConfigureMSALFromConfig(this IServiceCollection services, IConfigurationSection msalConfigSection)
145 {
146 ArgumentNullException.ThrowIfNull(msalConfigSection);
147 services.Configure<MicrosoftIdentityApplicationOptions>(msalConfigSection);
148 return services;
149 }
150
151 private static IServiceCollection ConfigureMSALWithSecret(this IServiceCollection services, string tenantId, string clientId, string clientSecret)
152 {
153 ArgumentNullException.ThrowIfNullOrWhiteSpace(tenantId);
154 ArgumentNullException.ThrowIfNullOrWhiteSpace(clientId);
155 ArgumentNullException.ThrowIfNullOrWhiteSpace(clientSecret);
156
157 services.Configure<MicrosoftIdentityApplicationOptions>(options =>
158 {
159 // TODO: Make Instance configurable
160 options.Instance = "https://login.microsoftonline.com/";
161 options.TenantId = tenantId;
162 options.ClientId = clientId;
163 options.ClientCredentials = [
164 new CredentialDescription()
165 {
166 SourceType = CredentialSource.ClientSecret,
167 ClientSecret = clientSecret
168 }
169 ];
170 });
171 return services;
172 }
173
174 private static IServiceCollection ConfigureMSALWithFIC(this IServiceCollection services, string tenantId, string clientId, string? ficClientId)
175 {
176 ArgumentNullException.ThrowIfNullOrWhiteSpace(tenantId);
177 ArgumentNullException.ThrowIfNullOrWhiteSpace(clientId);
178
179 var ficCredential = new CredentialDescription()
180 {
181 SourceType = CredentialSource.SignedAssertionFromManagedIdentity,
182 };
183 if (!string.IsNullOrEmpty(ficClientId) && !IsSystemAssignedManagedIdentity(ficClientId))
184 {
185 ficCredential.ManagedIdentityClientId = ficClientId;
186 }
187
188 services.Configure<MicrosoftIdentityApplicationOptions>(options =>
189 {
190 // TODO: Make Instance configurable
191 options.Instance = "https://login.microsoftonline.com/";
192 options.TenantId = tenantId;
193 options.ClientId = clientId;
194 options.ClientCredentials = [
195 ficCredential
196 ];
197 });
198 return services;
199 }
200
201 private static IServiceCollection ConfigureMSALWithUMI(this IServiceCollection services, string tenantId, string clientId, string? managedIdentityClientId = null)
202 {
203 ArgumentNullException.ThrowIfNullOrWhiteSpace(tenantId);
204 ArgumentNullException.ThrowIfNullOrWhiteSpace(clientId);
205
206 // Register ManagedIdentityOptions for BotAuthenticationHandler to use
207 bool isSystemAssigned = IsSystemAssignedManagedIdentity(managedIdentityClientId);
208 string? umiClientId = isSystemAssigned ? null : (managedIdentityClientId ?? clientId);
209
210 services.Configure<ManagedIdentityOptions>(options =>
211 {
212 options.UserAssignedClientId = umiClientId;
213 });
214
215 services.Configure<MicrosoftIdentityApplicationOptions>(options =>
216 {
217 // TODO: Make Instance configurable
218 options.Instance = "https://login.microsoftonline.com/";
219 options.TenantId = tenantId;
220 options.ClientId = clientId;
221 });
222 return services;
223 }
224
225 private static IServiceCollection ConfigureMSALFromBotConfig(this IServiceCollection services, BotConfig botConfig, ILogger logger)
226 {
227 ArgumentNullException.ThrowIfNull(botConfig);
228 if (!string.IsNullOrEmpty(botConfig.ClientSecret))
229 {
230 _logUsingClientSecret(logger, null);
231 services.ConfigureMSALWithSecret(botConfig.TenantId, botConfig.ClientId, botConfig.ClientSecret);
232 }
233 else if (string.IsNullOrEmpty(botConfig.FicClientId) || botConfig.FicClientId == botConfig.ClientId)
234 {
235 _logUsingUMI(logger, null);
236 services.ConfigureMSALWithUMI(botConfig.TenantId, botConfig.ClientId, botConfig.FicClientId);
237 }
238 else
239 {
240 bool isSystemAssigned = IsSystemAssignedManagedIdentity(botConfig.FicClientId);
241 _logUsingFIC(logger, isSystemAssigned ? "System-Assigned" : "User-Assigned", null);
242 services.ConfigureMSALWithFIC(botConfig.TenantId, botConfig.ClientId, botConfig.FicClientId);
243 }
244 return services;
245 }
246
247 /// <summary>
248 /// Determines if the provided client ID represents a system-assigned managed identity.
249 /// </summary>
250 private static bool IsSystemAssignedManagedIdentity(string? clientId)
251 => string.Equals(clientId, BotConfig.SystemManagedIdentityIdentifier, StringComparison.OrdinalIgnoreCase);
252
253 private static readonly Action<ILogger, Exception?> _logUsingBFConfig =
254 LoggerMessage.Define(LogLevel.Debug, new(1), "Configuring MSAL from Bot Framework configuration");
255 private static readonly Action<ILogger, Exception?> _logUsingCoreConfig =
256 LoggerMessage.Define(LogLevel.Debug, new(2), "Configuring MSAL from Core bot configuration");
257 private static readonly Action<ILogger, string, Exception?> _logUsingSectionConfig =
258 LoggerMessage.Define<string>(LogLevel.Debug, new(3), "Configuring MSAL from {SectionName} configuration section");
259 private static readonly Action<ILogger, Exception?> _logUsingClientSecret =
260 LoggerMessage.Define(LogLevel.Debug, new(4), "Configuring authentication with client secret");
261 private static readonly Action<ILogger, Exception?> _logUsingUMI =
262 LoggerMessage.Define(LogLevel.Debug, new(5), "Configuring authentication with User-Assigned Managed Identity");
263 private static readonly Action<ILogger, string, Exception?> _logUsingFIC =
264 LoggerMessage.Define<string>(LogLevel.Debug, new(6), "Configuring authentication with Federated Identity Credential (Managed Identity) with {IdentityType} Managed Identity");
265 private static readonly Action<ILogger, Exception?> _logAuthConfigNotFound =
266 LoggerMessage.Define(LogLevel.Warning, new(7), "Authentication configuration not found. Running without Auth");
267
268
269}
270