microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
next/core

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

227lines · modecode

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