chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Agents.CopilotStudio.Client;
using Microsoft.Agents.CopilotStudio.Client.Discovery;
using Microsoft.Extensions.Configuration;
namespace CopilotStudio.IntegrationTests.Support;
/// <summary>
/// <see cref="ConnectionSettings"/> with additional properties to specify Application (Client) Id,
/// Tenant Id, and optionally the Application Client secret.
/// </summary>
internal sealed class CopilotStudioConnectionSettings : ConnectionSettings
{
/// <summary>
/// Application ID for creating the authentication for the connection
/// </summary>
public string AppClientId { get; }
/// <summary>
/// Application secret for creating the authentication for the connection
/// </summary>
public string? AppClientSecret { get; }
/// <summary>
/// Tenant ID for creating the authentication for the connection
/// </summary>
public string TenantId { get; }
/// <summary>
/// Use interactive or service connection for authentication.
/// Defaults to true, meaning interactive authentication will be used.
/// </summary>
public bool UseInteractiveAuthentication { get; set; } = true;
/// <summary>
/// Instantiate a new instance of the <see cref="CopilotStudioConnectionSettings"/> from provided settings.
/// </summary>
public CopilotStudioConnectionSettings(string tenantId, string appClientId, string? appClientSecret = null)
{
this.TenantId = tenantId;
this.AppClientId = appClientId;
this.AppClientSecret = appClientSecret;
this.Cloud = PowerPlatformCloud.Prod;
this.CopilotAgentType = AgentType.Published;
}
/// <summary>
/// Instantiate a new instance of the <see cref="CopilotStudioConnectionSettings"/> from a configuration section.
/// </summary>
/// <param name="config"></param>
/// <exception cref="ArgumentException"></exception>
public CopilotStudioConnectionSettings(IConfigurationSection config)
: base(config)
{
this.AppClientId = config[nameof(this.AppClientId)] ?? throw new ArgumentException($"{nameof(this.AppClientId)} not found in config");
this.TenantId = config[nameof(this.TenantId)] ?? throw new ArgumentException($"{nameof(this.TenantId)} not found in config");
this.AppClientSecret = config[nameof(this.AppClientSecret)];
}
}
@@ -0,0 +1,140 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.CopilotStudio.Client;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;
using Microsoft.Shared.Diagnostics;
namespace CopilotStudio.IntegrationTests.Support;
#pragma warning disable CA1812 // Internal class that is apparently never instantiated.
/// <summary>
/// A <see cref="HttpClientHandler"/> that adds an authentication token to the request headers for Copilot Studio API calls.
/// </summary>
/// <remarks>
/// For more information on how to setup various authentication flows, see the Microsoft Identity documentation at https://aka.ms/msal.
/// </remarks>
internal sealed class CopilotStudioTokenHandler : HttpClientHandler
{
private const string AuthenticationHeader = "Bearer";
private const string CacheFolderName = "mcs_client_console";
private const string KeyChainServiceName = "copilot_studio_client_app";
private const string KeyChainAccountName = "copilot_studio_client";
private readonly CopilotStudioConnectionSettings _settings;
private readonly string[] _scopes;
private IConfidentialClientApplication? _clientApplication;
/// <summary>
/// Initializes a new instance of the <see cref="CopilotStudioTokenHandler"/> class with the specified connection settings.
/// </summary>
/// <param name="settings">The connection settings for Copilot Studio.</param>
public CopilotStudioTokenHandler(CopilotStudioConnectionSettings settings)
{
Throw.IfNull(settings);
this._settings = settings;
this._scopes = [CopilotClient.ScopeFromSettings(this._settings)];
}
/// <inheritdoc/>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Headers.Authorization is null)
{
AuthenticationResult authResponse = await this.AuthenticateAsync(cancellationToken).ConfigureAwait(false);
request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeader, authResponse.AccessToken);
}
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
private Task<AuthenticationResult> AuthenticateAsync(CancellationToken cancellationToken) =>
this._settings.UseInteractiveAuthentication ?
this.AuthenticateInteractiveAsync(cancellationToken) :
this.AuthenticateServiceAsync(cancellationToken);
private async Task<AuthenticationResult> AuthenticateServiceAsync(CancellationToken cancellationToken)
{
if (this._clientApplication is null)
{
this._clientApplication = ConfidentialClientApplicationBuilder.Create(this._settings.AppClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, this._settings.TenantId)
.WithClientSecret(this._settings.AppClientSecret)
.Build();
MsalCacheHelper tokenCacheHelper = await CreateCacheHelperAsync("AppTokenCache").ConfigureAwait(false);
tokenCacheHelper.RegisterCache(this._clientApplication.AppTokenCache);
}
AuthenticationResult authResponse;
authResponse = await this._clientApplication.AcquireTokenForClient(this._scopes).ExecuteAsync(cancellationToken).ConfigureAwait(false);
return authResponse;
}
private async Task<AuthenticationResult> AuthenticateInteractiveAsync(CancellationToken cancellationToken = default!)
{
IPublicClientApplication app =
PublicClientApplicationBuilder.Create(this._settings.AppClientId)
.WithAuthority(AadAuthorityAudience.AzureAdMyOrg)
.WithTenantId(this._settings.TenantId)
.WithRedirectUri("http://localhost")
.Build();
MsalCacheHelper tokenCacheHelper = await CreateCacheHelperAsync("TokenCache").ConfigureAwait(false);
tokenCacheHelper.RegisterCache(app.UserTokenCache);
IEnumerable<IAccount> accounts = await app.GetAccountsAsync().ConfigureAwait(false);
IAccount? account = accounts.FirstOrDefault();
AuthenticationResult authResponse;
try
{
authResponse = await app.AcquireTokenSilent(this._scopes, account).ExecuteAsync(cancellationToken).ConfigureAwait(false);
}
catch (MsalUiRequiredException)
{
authResponse = await app.AcquireTokenInteractive(this._scopes).ExecuteAsync(cancellationToken).ConfigureAwait(false);
}
return authResponse;
}
private static async Task<MsalCacheHelper> CreateCacheHelperAsync(string cacheFileName)
{
string currentDir = Path.Combine(AppContext.BaseDirectory, CacheFolderName);
if (!Directory.Exists(currentDir))
{
Directory.CreateDirectory(currentDir);
}
StorageCreationPropertiesBuilder storageProperties = new(cacheFileName, currentDir);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
storageProperties.WithLinuxUnprotectedFile();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
storageProperties.WithMacKeyChain(KeyChainServiceName, KeyChainAccountName);
}
return await MsalCacheHelper.CreateAsync(storageProperties.Build()).ConfigureAwait(false);
}
}