microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
samples/migration-bot

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

229lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Linq;
5using Microsoft.AspNetCore.Builder;
6using Microsoft.AspNetCore.Http;
7using Microsoft.AspNetCore.Routing;
8using Microsoft.Extensions.Configuration;
9using Microsoft.Extensions.DependencyInjection;
10using Microsoft.Extensions.Logging;
11using Microsoft.Extensions.Logging.Abstractions;
12using Microsoft.Extensions.Options;
13using Microsoft.Identity.Abstractions;
14using Microsoft.Identity.Web;
15using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
16
17namespace Microsoft.Teams.Bot.Core.Hosting;
18
19/// <summary>
20/// Provides extension methods for registering bot application clients and related authentication services with the
21/// dependency injection container.
22/// </summary>
23/// <remarks>This class is intended to be used during application startup to configure HTTP clients, token
24/// acquisition, and agent identity services required for bot-to-bot communication. The configuration section specified
25/// by the Azure Active Directory (AAD) configuration name is used to bind authentication options. Typically, these
26/// methods are called in the application's service configuration pipeline.</remarks>
27public static class AddBotApplicationExtensions
28{
29 /// <summary>
30 /// Initializes the default route
31 /// </summary>
32 /// <param name="endpoints"></param>
33 /// <param name="routePath"></param>
34 /// <returns></returns>
35 public static BotApplication UseBotApplication(
36 this IEndpointRouteBuilder endpoints,
37 string routePath = "api/messages")
38 => UseBotApplication<BotApplication>(endpoints, routePath);
39
40 /// <summary>
41 /// Configures the application to handle bot messages at the specified route and returns the registered bot
42 /// application instance.
43 /// </summary>
44 /// <remarks>This method adds authentication and authorization middleware to the HTTP pipeline and maps
45 /// a POST endpoint for bot messages. The endpoint requires authorization. Ensure that the bot application
46 /// is registered in the service container before calling this method.</remarks>
47 /// <typeparam name="TApp">The type of the bot application to use. Must inherit from BotApplication.</typeparam>
48 /// <param name="endpoints">The endpoint route builder used to configure endpoints.</param>
49 /// <param name="routePath">The route path at which to listen for incoming bot messages. Defaults to "api/messages".</param>
50 /// <returns>The registered bot application instance of type TApp.</returns>
51 /// <exception cref="InvalidOperationException">Thrown if the bot application of type TApp is not registered in the application's service container.</exception>
52 public static TApp UseBotApplication<TApp>(
53 this IEndpointRouteBuilder endpoints,
54 string routePath = "api/messages")
55 where TApp : BotApplication
56 {
57 ArgumentNullException.ThrowIfNull(endpoints);
58
59 // Add authentication and authorization middleware to the pipeline
60 // This is safe because WebApplication implements both IEndpointRouteBuilder and IApplicationBuilder
61 if (endpoints is IApplicationBuilder app)
62 {
63 app.UseAuthentication();
64 app.UseAuthorization();
65 }
66
67 TApp botApp = endpoints.ServiceProvider.GetService<TApp>() ?? throw new InvalidOperationException("Application not registered");
68
69 endpoints.MapPost(routePath, (HttpContext httpContext, CancellationToken cancellationToken)
70 => botApp.ProcessAsync(httpContext, cancellationToken)
71 ).RequireAuthorization();
72
73 return botApp;
74 }
75
76 /// <summary>
77 /// Registers the default bot application and its dependencies in the service collection.
78 /// </summary>
79 /// <param name="services">The service collection to add services to.</param>
80 /// <param name="sectionName">The configuration section name containing Azure AD settings. Defaults to "AzureAd".</param>
81 /// <returns>The service collection for method chaining.</returns>
82 public static IServiceCollection AddBotApplication(this IServiceCollection services, string sectionName = "AzureAd")
83 => services.AddBotApplication<BotApplication>(sectionName);
84
85 /// <summary>
86 /// Registers a custom bot application and its dependencies in the service collection.
87 /// </summary>
88 /// <typeparam name="TApp">The custom bot application type that inherits from BotApplication.</typeparam>
89 /// <param name="services">The service collection to add services to.</param>
90 /// <param name="sectionName">The configuration section name containing Azure AD settings. Defaults to "AzureAd".</param>
91 /// <returns>The service collection for method chaining.</returns>
92 public static IServiceCollection AddBotApplication<TApp>(this IServiceCollection services, string sectionName = "AzureAd") where TApp : BotApplication
93 {
94 BotConfig botConfig = BotConfig.Resolve(services, sectionName);
95
96 services.AddBotApplication<TApp>(botConfig);
97
98 return services;
99 }
100
101 /// <summary>
102 /// Registers a custom bot application and its dependencies in the service collection.
103 /// </summary>
104 /// <typeparam name="TApp">The custom bot application type that inherits from BotApplication.</typeparam>
105 /// <param name="services">The service collection to add services to.</param>
106 /// <param name="botConfig">The configuration containing Azure AD settings.</param>
107 /// <returns>The service collection for method chaining.</returns>
108 internal static IServiceCollection AddBotApplication<TApp>(this IServiceCollection services, BotConfig botConfig) where TApp : BotApplication
109 {
110 services.AddSingleton<BotApplicationOptions>(sp =>
111 {
112 IConfiguration config = sp.GetRequiredService<IConfiguration>();
113 return new BotApplicationOptions
114 {
115 AppId = botConfig.ClientId
116 };
117 });
118 services.AddHttpContextAccessor();
119 services.AddBotAuthorization(botConfig);
120 services.AddConversationClient(botConfig);
121 services.AddUserTokenClient(botConfig);
122 services.AddSingleton<TApp>();
123 return services;
124 }
125
126 /// <summary>
127 /// Adds conversation client to the service collection.
128 /// </summary>
129 /// <param name="services">service collection</param>
130 /// <param name="sectionName">Configuration Section name, defaults to AzureAD</param>
131 /// <returns></returns>
132 public static IServiceCollection AddConversationClient(this IServiceCollection services, string sectionName = "AzureAd")
133 {
134 BotConfig botConfig = BotConfig.Resolve(services, sectionName);
135 return services.AddConversationClient(botConfig);
136 }
137
138 /// <summary>
139 /// Adds user token client to the service collection.
140 /// </summary>
141 /// <param name="services">service collection</param>
142 /// <param name="sectionName">Configuration Section name, defaults to AzureAD</param>
143 /// <returns></returns>
144 public static IServiceCollection AddUserTokenClient(this IServiceCollection services, string sectionName = "AzureAd")
145 {
146 BotConfig botConfig = BotConfig.Resolve(services, sectionName);
147 return services.AddUserTokenClient(botConfig);
148 }
149
150 /// <summary>
151 /// Adds conversation client to the service collection using an already-resolved BotConfig.
152 /// </summary>
153 private static IServiceCollection AddConversationClient(this IServiceCollection services, BotConfig botConfig) =>
154 services.AddBotClient<ConversationClient>(ConversationClient.ConversationHttpClientName, botConfig);
155
156 /// <summary>
157 /// Adds user token client to the service collection using an already-resolved BotConfig.
158 /// </summary>
159 private static IServiceCollection AddUserTokenClient(this IServiceCollection services, BotConfig botConfig) =>
160 services.AddBotClient<UserTokenClient>(UserTokenClient.UserTokenHttpClientName, botConfig);
161
162 internal static IServiceCollection AddBotClient<TClient>(
163 this IServiceCollection services,
164 string httpClientName,
165 BotConfig botConfig) where TClient : class
166 {
167 // Register options using values from BotConfig
168 services.AddOptions<BotClientOptions>()
169 .Configure(options =>
170 {
171 options.Scope = botConfig.Scope;
172 options.SectionName = botConfig.SectionName;
173 });
174
175 // TODO: This shouldn't be called multiple times. It will being called once for each client we support.
176 services
177 .AddHttpClient()
178 .AddTokenAcquisition(true)
179 .AddInMemoryTokenCaches()
180 .AddAgentIdentities();
181
182 ILogger logger = GetLoggerFromServices(services);
183
184 if (services.ConfigureMSAL(botConfig, logger))
185 {
186 services.AddHttpClient<TClient>(httpClientName)
187 .AddHttpMessageHandler(sp =>
188 {
189 BotClientOptions botOptions = sp.GetRequiredService<IOptions<BotClientOptions>>().Value;
190 return new BotAuthenticationHandler(
191 sp.GetRequiredService<IAuthorizationHeaderProvider>(),
192 sp.GetRequiredService<ILogger<BotAuthenticationHandler>>(),
193 botOptions.Scope,
194 sp.GetService<IOptions<ManagedIdentityOptions>>());
195 });
196 }
197 else
198 {
199 services.AddHttpClient<TClient>(httpClientName);
200 }
201
202 return services;
203 }
204
205 /// <summary>
206 /// Gets a logger instance from the service collection.
207 /// If the logger factory is not available as an instance, builds a temporary service provider to create the logger.
208 /// </summary>
209 /// <param name="services">The service collection to extract the logger from.</param>
210 /// <param name="categoryType">The type to use for the logger category. If null, uses AddBotApplicationExtensions.</param>
211 /// <returns>An ILogger instance, or NullLogger if no logger factory is registered.</returns>
212 internal static ILogger GetLoggerFromServices(IServiceCollection services, Type? categoryType = null)
213 {
214 ServiceDescriptor? loggerFactoryDescriptor = services.FirstOrDefault(d => d.ServiceType == typeof(ILoggerFactory));
215 ILoggerFactory? loggerFactory = loggerFactoryDescriptor?.ImplementationInstance as ILoggerFactory;
216
217 // If logger factory is available as an instance, use it directly
218 if (loggerFactory != null)
219 {
220 return loggerFactory.CreateLogger(categoryType ?? typeof(AddBotApplicationExtensions));
221 }
222
223 // Otherwise, build a temporary service provider to create the logger
224 using ServiceProvider tempProvider = services.BuildServiceProvider();
225 ILoggerFactory? tempFactory = tempProvider.GetService<ILoggerFactory>();
226 return (ILogger?)tempFactory?.CreateLogger(categoryType ?? typeof(AddBotApplicationExtensions))
227 ?? Extensions.Logging.Abstractions.NullLogger.Instance;
228 }
229}
230