diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index b157db95..01278f1a 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32505.173 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.10607.193 main MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{4269F3C3-6B42-419B-B64A-3E6DC0F1574A}" EndProject @@ -123,6 +123,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FullAuthentication", "sampl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OBOAuthorization", "samples\Authorization\OBOAuthorization\OBOAuthorization.csproj", "{0E1394C7-045C-2BDC-65E8-D42B2F31EF46}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsAgent", "samples\Teams\TeamsAgent\TeamsAgent.csproj", "{D6410977-B795-C315-CC94-C2482E84BB4A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -305,6 +307,10 @@ Global {0E1394C7-045C-2BDC-65E8-D42B2F31EF46}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E1394C7-045C-2BDC-65E8-D42B2F31EF46}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E1394C7-045C-2BDC-65E8-D42B2F31EF46}.Release|Any CPU.Build.0 = Release|Any CPU + {D6410977-B795-C315-CC94-C2482E84BB4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6410977-B795-C315-CC94-C2482E84BB4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6410977-B795-C315-CC94-C2482E84BB4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6410977-B795-C315-CC94-C2482E84BB4A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -367,6 +373,7 @@ Global {E951C602-E9ED-F77D-8FC9-5272DB90D1DD} = {674A812C-7287-4883-97F9-697D83750648} {A6785A2A-A4C2-8F38-E9BB-4C5FD229F1F9} = {674A812C-7287-4883-97F9-697D83750648} {0E1394C7-045C-2BDC-65E8-D42B2F31EF46} = {0F9D3F0D-C131-4D3B-A86F-59CC781E8A02} + {D6410977-B795-C315-CC94-C2482E84BB4A} = {674A812C-7287-4883-97F9-697D83750648} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs deleted file mode 100644 index 58338c6a..00000000 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Builder; -using Microsoft.Agents.Builder.App; -using Microsoft.Agents.Builder.App.AdaptiveCards; -using Microsoft.Agents.Builder.App.UserAuth; -using Microsoft.Agents.Extensions.Teams.App.TaskModules; -using Microsoft.Agents.Storage; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; - -namespace Microsoft.Agents.Extensions.Teams.App -{ - public class TeamsApplicationOptions : AgentApplicationOptions - { - public TeamsApplicationOptions(IStorage storage) : base(storage) { } - - public TeamsApplicationOptions(IServiceProvider sp, IConfiguration configuration, IChannelAdapter channelAdapter, IStorage storage, UserAuthorizationOptions authOptions = null, AdaptiveCardsOptions cardOptions = null, IList fileDownloaders = null, string configurationSection = "AgentApplication") - : base(sp, configuration, channelAdapter, storage, authOptions, cardOptions, fileDownloaders, configurationSection) - { - } - - /// - /// Optional. Options used to customize the processing of Task Modules requests. - /// - public TaskModulesOptions? TaskModules { get; set; } - } -} diff --git a/src/samples/FullAuthentication/AspNetExtensions.cs b/src/samples/FullAuthentication/AspNetExtensions.cs index 24f17470..5349300f 100644 --- a/src/samples/FullAuthentication/AspNetExtensions.cs +++ b/src/samples/FullAuthentication/AspNetExtensions.cs @@ -67,7 +67,7 @@ public static void AddAgentAspNetAuthentication(this IServiceCollection services if (!tokenValidationSection.Exists()) { - logger?.LogError("Missing configuration section '{tokenValidationSectionName}'. This section is required to be present in appsettings.json",tokenValidationSectionName); + logger?.LogError("Missing configuration section '{tokenValidationSectionName}'. This section is required to be present in appsettings.json", tokenValidationSectionName); throw new InvalidOperationException($"Missing configuration section '{tokenValidationSectionName}'. This section is required to be present in appsettings.json"); } diff --git a/src/samples/Teams/TeamsAgent/AspNetExtensions.cs b/src/samples/Teams/TeamsAgent/AspNetExtensions.cs new file mode 100644 index 00000000..317accbf --- /dev/null +++ b/src/samples/Teams/TeamsAgent/AspNetExtensions.cs @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Protocols; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Validators; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace TeamsAgent; + +public static class AspNetExtensions +{ + private static readonly ConcurrentDictionary> _openIdMetadataCache = new(); + + /// + /// Adds token validation typical for ABS/SMBA and agent-to-agent. + /// default to Azure Public Cloud. + /// + /// + /// + /// Name of the config section to read. + /// Optional logger to use for authentication event logging. + /// + /// Configuration: + /// + /// "TokenValidation": { + /// "Audiences": [ + /// "{required:agent-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// + /// + /// `IsGov` can be omitted, in which case public Azure Bot Service and Azure Cloud metadata urls are used. + /// `ValidIssuers` can be omitted, in which case the Public Azure Bot Service issuers are used. + /// `TenantId` can be omitted if the Agent is not being called by another Agent. Otherwise it is used to add other known issuers. Only when `ValidIssuers` is omitted. + /// `AzureBotServiceOpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. + /// `OpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. + /// `AzureBotServiceTokenHandling` defaults to true and should always be true until Azure Bot Service sends Entra ID token. + /// + public static void AddAgentAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation", ILogger logger = null!) + { + IConfigurationSection tokenValidationSection = configuration.GetSection(tokenValidationSectionName); + List validTokenIssuers = tokenValidationSection.GetSection("ValidIssuers").Get>()!; + List audiences = tokenValidationSection.GetSection("Audiences").Get>()!; + + if (!tokenValidationSection.Exists()) + { + logger?.LogError("Missing configuration section '{tokenValidationSectionName}'. This section is required to be present in appsettings.json", tokenValidationSectionName); + throw new InvalidOperationException($"Missing configuration section '{tokenValidationSectionName}'. This section is required to be present in appsettings.json"); + } + + // If ValidIssuers is empty, default for ABS Public Cloud + if (validTokenIssuers == null || validTokenIssuers.Count == 0) + { + validTokenIssuers = + [ + "https://api.botframework.com", + "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", + "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", + "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", + "https://sts.windows.net/69e9b82d-4842-4902-8d1e-abc5b98a55e8/", + "https://login.microsoftonline.com/69e9b82d-4842-4902-8d1e-abc5b98a55e8/v2.0", + ]; + + string? tenantId = tokenValidationSection["TenantId"]; + if (!string.IsNullOrEmpty(tenantId)) + { + validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId)); + validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId)); + } + } + + if (audiences == null || audiences.Count == 0) + { + throw new ArgumentException($"{tokenValidationSectionName}:Audiences requires at least one value"); + } + + bool isGov = tokenValidationSection.GetValue("IsGov", false); + bool azureBotServiceTokenHandling = tokenValidationSection.GetValue("AzureBotServiceTokenHandling", true); + + // If the `AzureBotServiceOpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate ABS tokens. + string? azureBotServiceOpenIdMetadataUrl = tokenValidationSection["AzureBotServiceOpenIdMetadataUrl"]; + if (string.IsNullOrEmpty(azureBotServiceOpenIdMetadataUrl)) + { + azureBotServiceOpenIdMetadataUrl = isGov ? AuthenticationConstants.GovAzureBotServiceOpenIdMetadataUrl : AuthenticationConstants.PublicAzureBotServiceOpenIdMetadataUrl; + } + + // If the `OpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate Entra ID tokens. + string? openIdMetadataUrl = tokenValidationSection["OpenIdMetadataUrl"]; + if (string.IsNullOrEmpty(openIdMetadataUrl)) + { + openIdMetadataUrl = isGov ? AuthenticationConstants.GovOpenIdMetadataUrl : AuthenticationConstants.PublicOpenIdMetadataUrl; + } + + TimeSpan openIdRefreshInterval = tokenValidationSection.GetValue("OpenIdMetadataRefresh", BaseConfigurationManager.DefaultAutomaticRefreshInterval); + + _ = services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(5), + ValidIssuers = validTokenIssuers, + ValidAudiences = audiences, + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + }; + + // Using Microsoft.IdentityModel.Validators + options.TokenValidationParameters.EnableAadSigningKeyIssuerValidation(); + + options.Events = new JwtBearerEvents + { + // Create a ConfigurationManager based on the requestor. This is to handle ABS non-Entra tokens. + OnMessageReceived = async context => + { + string authorizationHeader = context.Request.Headers.Authorization.ToString(); + + if (string.IsNullOrEmpty(authorizationHeader)) + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + string[]? parts = authorizationHeader?.Split(' '); + if (parts?.Length != 2 || parts[0] != "Bearer") + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + JwtSecurityToken? token = new(parts[1]); + string issuer = token.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.IssuerClaim)?.Value!; + + if (azureBotServiceTokenHandling && AuthenticationConstants.BotFrameworkTokenIssuer.Equals(issuer)) + { + // Use the Azure Bot authority for this configuration manager + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(azureBotServiceOpenIdMetadataUrl, key => + { + return new ConfigurationManager(azureBotServiceOpenIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdRefreshInterval + }; + }); + } + else + { + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(openIdMetadataUrl, key => + { + return new ConfigurationManager(openIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdRefreshInterval + }; + }); + } + + await Task.CompletedTask.ConfigureAwait(false); + }, + + OnTokenValidated = context => + { + logger?.LogDebug("TOKEN Validated"); + return Task.CompletedTask; + }, + OnForbidden = context => + { + logger?.LogWarning("Forbidden: {m}", context.Result.ToString()); + return Task.CompletedTask; + }, + OnAuthenticationFailed = context => + { + logger?.LogWarning("Auth Failed {m}", context.Exception.ToString()); + return Task.CompletedTask; + } + }; + }); + } +} diff --git a/src/samples/Teams/TeamsAgent/Program.cs b/src/samples/Teams/TeamsAgent/Program.cs new file mode 100644 index 00000000..0760c3fa --- /dev/null +++ b/src/samples/Teams/TeamsAgent/Program.cs @@ -0,0 +1,17 @@ +using Microsoft.Agents.Builder; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Storage; +using TeamsAgent; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); +builder.Services.AddHttpClient(); +builder.Services.AddAgentAspNetAuthentication(builder.Configuration); +builder.AddAgentApplicationOptions(); +builder.AddAgent(); +builder.Services.AddSingleton(); +WebApplication app = builder.Build(); + +app.MapPost("/api/messages", + (HttpRequest request, HttpResponse response, IAgentHttpAdapter adapter, IAgent agent, CancellationToken cancellationToken) => + adapter.ProcessAsync(request, response, agent, cancellationToken)); +app.Run(); \ No newline at end of file diff --git a/src/samples/Teams/TeamsAgent/TeamsAgent.cs b/src/samples/Teams/TeamsAgent/TeamsAgent.cs new file mode 100644 index 00000000..d0e3220e --- /dev/null +++ b/src/samples/Teams/TeamsAgent/TeamsAgent.cs @@ -0,0 +1,206 @@ +using AdaptiveCards.Rendering; +using Microsoft.Agents.Builder; +using Microsoft.Agents.Builder.App; +using Microsoft.Agents.Builder.App.AdaptiveCards; +using Microsoft.Agents.Builder.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Extensions.Teams.App; +using Microsoft.Agents.Extensions.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Models; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace TeamsAgent +{ + public class TeamsAgent : AgentApplication + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + public TeamsAgent(AgentApplicationOptions options, IHttpClientFactory httpClientFactory, ILogger logger) : base(options) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + RegisterExtension(new TeamsAgentExtension(this), tae => + { + tae.OnMessageEdit(MessageEdited); + tae.MessageExtensions.OnQuery("findNuGetPackage", OnQuery); + tae.MessageExtensions.OnSelectItem(OnSelectItem); + }); + AdaptiveCards.OnSearch("dataset", OnSearchDS); + OnMessage("/help", (t, _, ct) => t.SendActivityAsync("TeamsAgent demo", cancellationToken: ct)); + OnMessageReactionsAdded(OnMessageReaction); + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); + OnActivity(ActivityTypes.Message, OnMessageAsync); + } + + private Task> OnSearchDS(ITurnContext turnContext, ITurnState turnState, Query query, CancellationToken cancellationToken) + { + var qt = query.Parameters.QueryText; + IList result = new List() + { + new AdaptiveCardsSearchResult("search", qt) + }; + return Task.FromResult(result); + } + + private async Task OnSelectItem(ITurnContext turnContext, ITurnState turnState, object item, CancellationToken cancellationToken) + { + PackageItem? package = JsonSerializer.Deserialize((JsonElement)item); + if (package is null) + { + await turnContext.SendActivityAsync("selected item is not a packageItem", cancellationToken: cancellationToken); + _logger.LogWarning("Selected Item cannot be deserialized as a PackageItem"); + return null!; + } + + await turnContext.SendActivityAsync("selected item " + JsonSerializer.Serialize(item), cancellationToken: cancellationToken); + ThumbnailCard card = new() + { + Title = $"{package.PackageId}, {package.Version}", + Subtitle = package.Description, + Buttons = + [ + new() { Type = ActionTypes.OpenUrl, Title = "Nuget Package", Value = $"https://www.nuget.org/packages/{package.PackageId}" }, + new() { Type = ActionTypes.OpenUrl, Title = "Project", Value = package.ProjectUrl}, + ], + }; + + if (!string.IsNullOrEmpty(package.IconUrl)) + { + card.Images = [new(package.IconUrl, "Icon")]; + } + + MessagingExtensionAttachment attachment = new() + { + ContentType = ThumbnailCard.ContentType, + Content = card, + }; + + return await Task.FromResult(new MessagingExtensionResult + { + Type = "result", + AttachmentLayout = "list", + Attachments = [attachment] + }); + } + + private async Task OnQuery(ITurnContext turnContext, ITurnState turnState, Query> query, CancellationToken cancellationToken) + { + CommandValue cmd = ProtocolJsonSerializer.ToObject>(turnContext.Activity.Value); + if (cmd.CommandId != "findNuGetPackage") + { + _logger.LogWarning("Received unexpected commandID {cmdName}", cmd.CommandId); + return await Task.FromResult(new MessagingExtensionResult()); + } + + JsonElement el = query.Parameters.TryGetValue("NuGetPackageName"); + + if (el.ValueKind == JsonValueKind.Undefined) + { + return await Task.FromResult(new MessagingExtensionResult()); + } + + string text = el.GetString() ?? string.Empty; + + + IEnumerable packages = await FindPackages(text); + List attachments = [.. packages.Select(package => + { + string cardValue = $$$""" + { + "id": "{{{package.PackageId}}}", + "version" : "{{{package.Version}}}", + "description" : "{{{PackageItem.NormalizeString(package.Description!)}}}", + "projectUrl" : "{{{package.ProjectUrl}}}", + "iconUrl" : "{{{package.IconUrl}}}" + } + """; + + ThumbnailCard previewCard = new() { Title = package.PackageId, Tap = new CardAction { Type = "invoke", Value = cardValue } }; + if (!string.IsNullOrEmpty(package.IconUrl)) + { + previewCard.Images = [new CardImage(package.IconUrl, "Icon")]; + } + + MessagingExtensionAttachment attachment = new() + { + ContentType = HeroCard.ContentType, + Content = new HeroCard { Title = package.Id }, + Preview = previewCard.ToAttachment() + }; + + return attachment; + })]; + + return new MessagingExtensionResult + { + Type = "result", + AttachmentLayout = "list", + Attachments = attachments + + }; + } + + private async Task> FindPackages(string text) + { + HttpClient httpClient = _httpClientFactory.CreateClient(); + string jsonResult = await httpClient.GetStringAsync($"https://azuresearch-usnc.nuget.org/query?q=id:{text}&prerelease=true"); + JsonElement data = JsonDocument.Parse(jsonResult).RootElement.GetProperty("data"); + PackageItem[]? packages = data.Deserialize(); + return packages!; + } + + private Task OnMessageReaction(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => + turnContext.SendActivityAsync("Message Reaction: " + turnContext.Activity.ReactionsAdded[0].Type, cancellationToken: cancellationToken); + + private Task MessageEdited(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => + turnContext.SendActivityAsync("Message Edited: " + turnContext.Activity.Id, cancellationToken: cancellationToken); + + private Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => + turnContext.SendActivityAsync(MessageFactory.Text("Welcome to the TeamsAgent sample!"), cancellationToken); + + private async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + TeamsChannelAccount member = await TeamsInfo.GetMemberAsync(turnContext, turnContext.Activity.From.Id, cancellationToken); + string msg = member.Name ?? "not teams user"; + await turnContext.SendActivityAsync($"hi {msg}, use the '+' option on Teams message textbox to start the MessageExtension search", cancellationToken: cancellationToken); + } + } + + class Root + { + [JsonPropertyName("data")] + public PackageItem[]? Data { get; set; } + } + + class PackageItem + { + [JsonPropertyName("@id")] + public string? Id { get; set; } + + [JsonPropertyName("id")] + public string? PackageId { get; set; } + + [JsonPropertyName("version")] + public string? Version { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("projectUrl")] + public string? ProjectUrl { get; set; } + + [JsonPropertyName("iconUrl")] + public string? IconUrl { get; set; } + public static string NormalizeString(string value) + { + return value + .Replace("\r\n", " ") + .Replace("\r", " ") + .Replace("\n", " ") + .Replace("\"", "\\\""); + } + + } +} diff --git a/src/samples/Teams/TeamsAgent/TeamsAgent.csproj b/src/samples/Teams/TeamsAgent/TeamsAgent.csproj new file mode 100644 index 00000000..3f5222fa --- /dev/null +++ b/src/samples/Teams/TeamsAgent/TeamsAgent.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/src/samples/Teams/TeamsAgent/appsettings.json b/src/samples/Teams/TeamsAgent/appsettings.json new file mode 100644 index 00000000..69e2cb3f --- /dev/null +++ b/src/samples/Teams/TeamsAgent/appsettings.json @@ -0,0 +1,35 @@ +{ + "TokenValidation": { + "Audiences": [ + "" // this is the Client ID used for the Azure Bot + ], + "TenantId": "" + }, + + "Connections": { + "ServiceConnection": { + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/", + "ClientId": "", // this is the Client ID used for the Azure Bot + "ClientSecret": "", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + "ConnectionsMap": [ + { + "ServiceUrl": "*", + "Connection": "ServiceConnection" + } + ], + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/samples/Teams/TeamsAgent/manifest/color.png b/src/samples/Teams/TeamsAgent/manifest/color.png new file mode 100644 index 00000000..b8cf81af Binary files /dev/null and b/src/samples/Teams/TeamsAgent/manifest/color.png differ diff --git a/src/samples/Teams/TeamsAgent/manifest/outline.png b/src/samples/Teams/TeamsAgent/manifest/outline.png new file mode 100644 index 00000000..2c3bf6fa Binary files /dev/null and b/src/samples/Teams/TeamsAgent/manifest/outline.png differ diff --git a/src/samples/Teams/TeamsAgent/manifest/teams-manifest.TEMPLATE.jsonc b/src/samples/Teams/TeamsAgent/manifest/teams-manifest.TEMPLATE.jsonc new file mode 100644 index 00000000..d8ba1e3a --- /dev/null +++ b/src/samples/Teams/TeamsAgent/manifest/teams-manifest.TEMPLATE.jsonc @@ -0,0 +1,87 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.20/MicrosoftTeams.schema.json", + "version": "1.0.0", + "manifestVersion": "1.20", + "id": "<>", + "name": { + "short": "", + "full": "" + }, + "developer": { + "name": "", + "mpnId": "", + "websiteUrl": "", + "privacyUrl": "", + "termsOfUseUrl": "" + }, + "description": { + "short": "", + "full": "" + }, + "icons": { + "outline": "outline.png", + "color": "color.png" + }, + "accentColor": "#FFFFFF", + "staticTabs": [ + { + "entityId": "conversations", + "scopes": [ + "personal" + ] + }, + { + "entityId": "about", + "scopes": [ + "personal" + ] + } + ], + "bots": [ + { + "botId": "", + "scopes": [ + "personal", + "team", + "groupChat" + ], + "isNotificationOnly": false, + "supportsCalling": false, + "supportsVideo": false, + "supportsFiles": false + } + ], + "composeExtensions": [ + { + "botId": "", + "commands": [ + { + "id": "findNuGetPackage", + "type": "query", + "title": "findNuGetPackage", + "description": "findNuGetPackage", + "initialRun": true, + "fetchTask": false, + "context": [ + "commandBox", + "compose" + ], + "parameters": [ + { + "name": "NuGetPackageName", + "title": "NuGetPackageName", + "description": "NuGetPackageName", + "inputType": "text" + } + ] + } + ], + "canUpdateConfiguration": true + } + ], + "validDomains": [], + "webApplicationInfo": { + "id": "", + "resource": "" + } +} \ No newline at end of file