microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v2.0.8

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/test/Microsoft.Teams.Core.UnitTests/Hosting/AddBotApplicationExtensionsTests.cs

286lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using Microsoft.Extensions.Configuration;
5using Microsoft.Extensions.DependencyInjection;
6using Microsoft.Extensions.Options;
7using Microsoft.Identity.Abstractions;
8using Microsoft.Teams.Core.Hosting;
9
10namespace Microsoft.Teams.Core.UnitTests.Hosting;
11
12public class AddBotApplicationExtensionsTests
13{
14 private static ServiceProvider BuildServiceProvider(Dictionary<string, string?> configData, string? aadConfigSectionName = null)
15 {
16 IConfigurationRoot configuration = new ConfigurationBuilder()
17 .AddInMemoryCollection(configData)
18 .Build();
19
20 ServiceCollection services = new();
21 services.AddSingleton<IConfiguration>(configuration);
22 services.AddLogging();
23
24 if (aadConfigSectionName is null)
25 {
26 services.AddConversationClient();
27 }
28 else
29 {
30 services.AddConversationClient(aadConfigSectionName);
31 }
32
33 return services.BuildServiceProvider();
34 }
35
36 private static void AssertMsalOptions(ServiceProvider serviceProvider, string sectionName, string expectedClientId, string expectedTenantId, string expectedInstance = "https://login.microsoftonline.com/")
37 {
38 MicrosoftIdentityApplicationOptions msalOptions = serviceProvider
39 .GetRequiredService<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>()
40 .Get(sectionName);
41 Assert.Equal(expectedClientId, msalOptions.ClientId);
42 Assert.Equal(expectedTenantId, msalOptions.TenantId);
43 Assert.Equal(expectedInstance, msalOptions.Instance);
44 }
45
46 [Fact]
47 public void AddConversationClient_WithDefaultSection_ConfiguresFromSection()
48 {
49 // AzureAd is the default Section Name
50 // Arrange
51 Dictionary<string, string?> configData = new()
52 {
53 ["AzureAd:ClientId"] = "azuread-client-id",
54 ["AzureAd:TenantId"] = "azuread-tenant-id",
55 ["AzureAd:Instance"] = "https://login.microsoftonline.com/"
56 };
57
58 // Act
59 ServiceProvider serviceProvider = BuildServiceProvider(configData);
60
61 // Assert
62 AssertMsalOptions(serviceProvider, "AzureAd", "azuread-client-id", "azuread-tenant-id");
63 }
64
65 [Fact]
66 public void AddConversationClient_WithCustomSectionName_ConfiguresFromCustomSection()
67 {
68 // Arrange
69 Dictionary<string, string?> configData = new()
70 {
71 ["CustomAuth:ClientId"] = "custom-client-id",
72 ["CustomAuth:TenantId"] = "custom-tenant-id",
73 ["CustomAuth:Instance"] = "https://login.microsoftonline.com/"
74 };
75
76 // Act
77 ServiceProvider serviceProvider = BuildServiceProvider(configData, "CustomAuth");
78
79 // Assert
80 AssertMsalOptions(serviceProvider, "CustomAuth", "custom-client-id", "custom-tenant-id");
81 }
82
83 // --- BotApplicationOptions (AppId) tests ---
84
85 private static ServiceProvider BuildServiceProviderForBotApp(Dictionary<string, string?> configData, string? sectionName = null)
86 {
87 IConfigurationRoot configuration = new ConfigurationBuilder()
88 .AddInMemoryCollection(configData)
89 .Build();
90
91 ServiceCollection services = new();
92 services.AddSingleton<IConfiguration>(configuration);
93 services.AddLogging();
94
95 if (sectionName is null)
96 services.AddBotApplication();
97 else
98 services.AddBotApplication(sectionName);
99
100 return services.BuildServiceProvider();
101 }
102
103 private static string GetAppId(ServiceProvider serviceProvider) =>
104 serviceProvider.GetRequiredService<BotApplicationOptions>().AppId;
105
106 [Fact]
107 public void AddBotApplication_WithAzureAdSection_SetsAppIdFromSection()
108 {
109 // Arrange
110 Dictionary<string, string?> configData = new()
111 {
112 ["AzureAd:ClientId"] = "azuread-client-id",
113 ["AzureAd:TenantId"] = "azuread-tenant-id"
114 };
115
116 // Act
117 ServiceProvider serviceProvider = BuildServiceProviderForBotApp(configData);
118
119 // Assert
120 Assert.Equal("azuread-client-id", GetAppId(serviceProvider));
121 }
122
123 [Fact]
124 public void AddBotApplication_WithCustomSection_SetsAppIdFromCustomSection()
125 {
126 // Arrange
127 Dictionary<string, string?> configData = new()
128 {
129 ["CustomAuth:ClientId"] = "custom-client-id",
130 ["CustomAuth:TenantId"] = "custom-tenant-id"
131 };
132
133 // Act
134 ServiceProvider serviceProvider = BuildServiceProviderForBotApp(configData, "CustomAuth");
135
136 // Assert
137 Assert.Equal("custom-client-id", GetAppId(serviceProvider));
138 }
139
140 // --- ManagedIdentityOptions (UMI) tests ---
141
142 private static ServiceProvider BuildServiceProviderWithManagedIdentity(Dictionary<string, string?> configData, string? sectionName = null)
143 {
144 IConfigurationRoot configuration = new ConfigurationBuilder()
145 .AddInMemoryCollection(configData)
146 .Build();
147
148 ServiceCollection services = new();
149 services.AddSingleton<IConfiguration>(configuration);
150 services.AddLogging();
151
152 if (sectionName is null)
153 services.AddBotApplication();
154 else
155 services.AddBotApplication(sectionName);
156
157 return services.BuildServiceProvider();
158 }
159
160 [Fact]
161 public void AddBotApplication_WithNoClientCredentials_ConfiguresManagedIdentityOptions()
162 {
163 // Arrange: Configuration with ClientId/TenantId but NO ClientCredentials (implies UMI)
164 Dictionary<string, string?> configData = new()
165 {
166 ["AzureAd:ClientId"] = "umi-client-id",
167 ["AzureAd:TenantId"] = "tenant-id"
168 // No AzureAd:ClientCredentials
169 };
170
171 // Act
172 ServiceProvider serviceProvider = BuildServiceProviderWithManagedIdentity(configData);
173
174 // Assert: named options entry under the section name carries the UMI client id
175 ManagedIdentityOptions miOptions = serviceProvider
176 .GetRequiredService<IOptionsMonitor<ManagedIdentityOptions>>()
177 .Get("AzureAd");
178
179 Assert.NotNull(miOptions);
180 Assert.Equal("umi-client-id", miOptions.UserAssignedClientId);
181 }
182
183 [Fact]
184 public void AddBotApplication_WithClientCredentials_DoesNotConfigureUmiAsUserAssigned()
185 {
186 // Arrange: Configuration WITH ClientCredentials (app-only authentication, not UMI)
187 Dictionary<string, string?> configData = new()
188 {
189 ["AzureAd:ClientId"] = "app-client-id",
190 ["AzureAd:TenantId"] = "tenant-id",
191 ["AzureAd:ClientCredentials:0:SourceType"] = "ClientSecret",
192 ["AzureAd:ClientCredentials:0:ClientSecret"] = "secret-value"
193 };
194
195 // Act
196 ServiceProvider serviceProvider = BuildServiceProviderWithManagedIdentity(configData);
197
198 // Assert: when ClientCredentials are present, the named entry must remain empty
199 ManagedIdentityOptions miOptions = serviceProvider
200 .GetRequiredService<IOptionsMonitor<ManagedIdentityOptions>>()
201 .Get("AzureAd");
202
203 Assert.Null(miOptions.UserAssignedClientId);
204 }
205
206 [Fact]
207 public void AddBotApplication_WithCustomSectionNoClientCredentials_ConfiguresManagedIdentityFromCustomSection()
208 {
209 // Arrange: Custom section with no ClientCredentials
210 Dictionary<string, string?> configData = new()
211 {
212 ["CustomAuth:ClientId"] = "custom-umi-client-id",
213 ["CustomAuth:TenantId"] = "custom-tenant-id"
214 };
215
216 // Act
217 ServiceProvider serviceProvider = BuildServiceProviderWithManagedIdentity(configData, "CustomAuth");
218
219 // Assert
220 ManagedIdentityOptions miOptions = serviceProvider
221 .GetRequiredService<IOptionsMonitor<ManagedIdentityOptions>>()
222 .Get("CustomAuth");
223
224 Assert.NotNull(miOptions);
225 Assert.Equal("custom-umi-client-id", miOptions.UserAssignedClientId);
226 }
227
228 [Fact]
229 public void AddBotApplication_WithMultipleSections_IsolatesManagedIdentityPerSection()
230 {
231 // Arrange: one UMI section and one app-secret section in the same host.
232 Dictionary<string, string?> configData = new()
233 {
234 // UMI bot — no ClientCredentials
235 ["UmiBot:ClientId"] = "umi-client-id",
236 ["UmiBot:TenantId"] = "umi-tenant-id",
237
238 // App-secret bot — has ClientCredentials, must NOT be classified as UMI
239 ["SecretBot:ClientId"] = "secret-client-id",
240 ["SecretBot:TenantId"] = "secret-tenant-id",
241 ["SecretBot:ClientCredentials:0:SourceType"] = "ClientSecret",
242 ["SecretBot:ClientCredentials:0:ClientSecret"] = "secret-value"
243 };
244
245 IConfigurationRoot configuration = new ConfigurationBuilder()
246 .AddInMemoryCollection(configData)
247 .Build();
248
249 ServiceCollection services = new();
250 services.AddSingleton<IConfiguration>(configuration);
251 services.AddLogging();
252 services.AddBotApplication("UmiBot");
253 services.AddBotApplication("SecretBot");
254
255 // Act
256 using ServiceProvider serviceProvider = services.BuildServiceProvider();
257 IOptionsMonitor<ManagedIdentityOptions> monitor = serviceProvider
258 .GetRequiredService<IOptionsMonitor<ManagedIdentityOptions>>();
259
260 // Assert: UMI section gets its own ClientId; app-secret section is untouched.
261 Assert.Equal("umi-client-id", monitor.Get("UmiBot").UserAssignedClientId);
262 Assert.Null(monitor.Get("SecretBot").UserAssignedClientId);
263 }
264
265 [Fact]
266 public void AddBotApplication_WithNestedSectionPath_ConfiguresOptionsUnderFullSectionName()
267 {
268 // Arrange: Nested section path (e.g., "Auth:AzureAd")
269 Dictionary<string, string?> configData = new()
270 {
271 ["Auth:AzureAd:ClientId"] = "nested-client-id",
272 ["Auth:AzureAd:TenantId"] = "nested-tenant-id"
273 };
274
275 // Act
276 ServiceProvider serviceProvider = BuildServiceProviderWithManagedIdentity(configData, "Auth:AzureAd");
277
278 // Assert: Verify MSAL options are configured under the full section name (not just the leaf key)
279 MicrosoftIdentityApplicationOptions msalOptions = serviceProvider
280 .GetRequiredService<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>()
281 .Get("Auth:AzureAd");
282
283 Assert.Equal("nested-client-id", msalOptions.ClientId);
284 Assert.Equal("nested-tenant-id", msalOptions.TenantId);
285 }
286}
287