diff --git a/.github/workflows/build-complete-samples.yml b/.github/workflows/build-complete-samples.yml index 9e357bec34..a8d729cfea 100644 --- a/.github/workflows/build-complete-samples.yml +++ b/.github/workflows/build-complete-samples.yml @@ -174,7 +174,7 @@ jobs: - project_path: 'samples/tab-conversations/csharp/TabConversation/TabConversation.csproj' name: 'tab-conversations' - version: '6.0.x' + version: '10.0.x' - project_path: 'samples/tab-product-inspection/csharp/ProductInspection/ProductInspection.csproj' name: 'tab-product-inspection' @@ -190,7 +190,7 @@ jobs: - project_path: 'samples/msgext-message-reminder/csharp/MessagingExtensionReminder/MessagingExtensionReminder.csproj' name: 'msgext-message-reminder' - version: '6.0.x' + version: '10.0.x' - project_path: 'samples/msgext-search-sso-config/csharp/TeamsMessagingExtensionsSearchSSO.csproj' name: 'msgext-search-sso-config' @@ -494,7 +494,7 @@ jobs: - project_path: 'samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.csproj' name: 'bot-auth0-adaptivecard' - version: '6.0.x' + version: '10.0.x' - project_path: 'samples/agent-knowledge-hub/csharp/AgentKnowledgeHub/AgentKnowledgeHub.csproj' name: 'agent-knowledge-hub' diff --git a/.github/workflows/message-reminder-me-csharp.yml b/.github/workflows/message-reminder-me-csharp.yml index ad0d4c7c73..7ab85c8ad8 100644 --- a/.github/workflows/message-reminder-me-csharp.yml +++ b/.github/workflows/message-reminder-me-csharp.yml @@ -28,7 +28,7 @@ jobs: - name: Set up .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: '10.0.x' include-prerelease: true - name: Build app diff --git a/.github/workflows/tab-conversations-csharp.yml b/.github/workflows/tab-conversations-csharp.yml index 5c49961264..7791cab1d6 100644 --- a/.github/workflows/tab-conversations-csharp.yml +++ b/.github/workflows/tab-conversations-csharp.yml @@ -18,7 +18,7 @@ env: AZURE_WEBAPP_PACKAGE_PATH: 'samples/tab-conversations/csharp/published' # set this to the path to your web app project, defaults to the repository root PROJECT_PATH: 'samples/tab-conversations/csharp/TabConversation/TabConversation.csproj' AZURE_WEBAPP_NAME: 'tab-conversations-devcom' # set this to your application's name - DOTNET_VERSION: '6.0.x' # set this to the dot net version to use + DOTNET_VERSION: '10.0.x' # set this to the dot net version to use jobs: build: runs-on: windows-latest diff --git a/samples/bot-auth0-adaptivecard/csharp/AdapterWithErrorHandler.cs b/samples/bot-auth0-adaptivecard/csharp/AdapterWithErrorHandler.cs deleted file mode 100644 index 9a3d997872..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/AdapterWithErrorHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Builder.TraceExtensions; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Extensions.Logging; - -namespace Microsoft.BotBuilderSamples -{ - public class AdapterWithErrorHandler : CloudAdapter - { - public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger logger) - : base(auth, logger) - { - OnTurnError = async (turnContext, exception) => - { - // Log any leaked exception from the application. - // NOTE: In production environment, you should consider logging this to - // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how - // to add telemetry capture to your bot. - logger.LogError($"Exception caught : {exception.Message}"); - - // Uncomment below commented line for local debugging. - // await turnContext.SendActivityAsync($"Sorry, it looks like something went wrong. Exception Caught: {exception.Message}"); - - // Send a trace activity, which will be displayed in the Bot Framework Emulator - await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); - }; - } - } -} diff --git a/samples/bot-auth0-adaptivecard/csharp/Bots/TeamsConversationBot.cs b/samples/bot-auth0-adaptivecard/csharp/Bots/TeamsConversationBot.cs deleted file mode 100644 index a9f865163c..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/Bots/TeamsConversationBot.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Teams; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Bot.Schema; -using Microsoft.Bot.Schema.Teams; -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json.Linq; -using AdaptiveCards.Templating; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using System.Collections.Concurrent; -using System.Collections; -using Microsoft.AspNetCore.Http; -using static System.Net.Mime.MediaTypeNames; -using System.Net.Http.Headers; -using System.Net.Http; -using System.Text.Json; -using System.Web; -using static Microsoft.BotBuilderSamples.Controllers.AuthController; -using AdaptiveCards; - -namespace Microsoft.BotBuilderSamples.Bots -{ - public class TeamsConversationBot : TeamsActivityHandler - { - private string _appUrl; - private string _authDomain; - private string _authClientId; - private readonly TokenStore _authToken; - private readonly IHttpClientFactory _clientFactory; - public TeamsConversationBot(IConfiguration config, IHttpClientFactory clientFactory, TokenStore tokenStore) - { - _authDomain = config["Auth0:Domain"]; - _authClientId = config["Auth0:ClientId"]; - _clientFactory = clientFactory; - _appUrl = config["ApplicationUrl"]; - _authToken = tokenStore; - } - - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - var userId = turnContext.Activity.From.Id; - var text = turnContext.Activity.Text?.Trim().ToLowerInvariant(); - - if (text == "logout") - { - _authToken.RemoveToken(userId); // You need to implement this method in your token store - - // Optional: Auth0 logout URL (will clear Auth0 session too) - var logoutUrl = $"https://{_authDomain}/v2/logout?client_id={_authClientId}"; - - var card = new HeroCard - { - Title = "You've been logged out.", - Buttons = new List - { - new CardAction(ActionTypes.OpenUrl, "Logout from Auth0", value: logoutUrl) - } - }; - - await turnContext.SendActivityAsync(MessageFactory.Attachment(card.ToAttachment()), cancellationToken); - return; - } - - if (_authToken.TryGetToken(userId, out var accessToken)) - { - if (text == "profile details") - { - var client = _clientFactory.CreateClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var userInfoEndpoint = $"https://{_authDomain}/userinfo"; - var response = await client.GetAsync(userInfoEndpoint); - - if (response.IsSuccessStatusCode) - { - var content = await response.Content.ReadAsStringAsync(); - var profileData = JsonSerializer.Deserialize>(content); - - var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 3)) - { - Body = new List - { - new AdaptiveTextBlock("Auth0 Profile") - { - Size = AdaptiveTextSize.Large, - Weight = AdaptiveTextWeight.Bolder, - Wrap = true - }, - new AdaptiveImage(profileData["picture"].ToString()) - { - Size = AdaptiveImageSize.Medium, - Style = AdaptiveImageStyle.Person - }, - new AdaptiveTextBlock($"Name: {profileData["name"]}") - { - Wrap = true, - Weight = AdaptiveTextWeight.Default - }, - new AdaptiveTextBlock($"Email: {profileData["email"]}") - { - Wrap = true, - Weight = AdaptiveTextWeight.Default - } - } - }; - - var attachment = new Attachment - { - ContentType = AdaptiveCard.ContentType, - Content = card - }; - - await turnContext.SendActivityAsync(MessageFactory.Attachment(attachment), cancellationToken); - - await turnContext.SendActivityAsync(MessageFactory.Text($"Your profile Data: {JsonSerializer.Serialize(profileData)}"), cancellationToken); - } - else - { - await turnContext.SendActivityAsync(MessageFactory.Text("Failed to fetch profile details."), cancellationToken); - } - } - else - { - await turnContext.SendActivityAsync(MessageFactory.Text("Say 'profile details' to get your Auth0 profile or 'logout' to log out."), cancellationToken); - } - } - else - { - // No token yet - var loginUrl = GenerateLoginUrl(userId); - - var card = new HeroCard - { - Title = "Login Required", - Buttons = new List - { - new CardAction(ActionTypes.OpenUrl, "Login", value: loginUrl) - } - }; - - await turnContext.SendActivityAsync(MessageFactory.Attachment(card.ToAttachment()), cancellationToken); - } - } - - private string GenerateLoginUrl(string userId) - { - var domain = _authDomain; - if (!domain.StartsWith("https://")) - { - domain = "https://" + domain; - } - - var authUrl = $"{domain}/authorize" + - $"?response_type=code&client_id={_authClientId}" + - $"&redirect_uri={_appUrl}/api/auth/callback" + - $"&scope=openid profile email" + - $"&state={HttpUtility.UrlEncode(userId)}"; - - return authUrl; - } - } -} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/Controllers/AuthController.cs b/samples/bot-auth0-adaptivecard/csharp/Controllers/AuthController.cs deleted file mode 100644 index d719217994..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/Controllers/AuthController.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using System.Web; - -namespace Microsoft.BotBuilderSamples.Controllers -{ - [Route("api/auth")] - [ApiController] - public class AuthController : ControllerBase - { - private readonly IConfiguration _config; - private readonly IHttpClientFactory _clientFactory; - - public AuthController(IConfiguration config, IHttpClientFactory clientFactory) - { - _config = config; - _clientFactory = clientFactory; - } - - [HttpGet("callback")] - public async Task Callback( - [FromQuery] string code, - [FromQuery] string state, - [FromServices] TokenStore tokenStore) - { - var client = _clientFactory.CreateClient(); - var tokenEndpoint = $"https://{_config["Auth0:Domain"]}/oauth/token"; - - var response = await client.PostAsync(tokenEndpoint, new FormUrlEncodedContent(new Dictionary - { - {"grant_type", "authorization_code"}, - {"client_id", _config["Auth0:ClientId"]}, - {"client_secret", _config["Auth0:ClientSecret"]}, - {"code", code}, - {"redirect_uri", _config["ApplicationUrl"] + "/api/auth/callback"} - })); - - if (!response.IsSuccessStatusCode) - { - return BadRequest("Token exchange failed"); - } - - var content = await response.Content.ReadAsStringAsync(); - var tokenResult = JsonSerializer.Deserialize(content); - - tokenStore.SaveToken(state, tokenResult.access_token); - - return Redirect($"/auth-end.html?token={HttpUtility.UrlEncode(content)}"); - } - - - public class TokenStore - { - private readonly Dictionary _tokens = new(); - - public void SaveToken(string userId, string token) - { - _tokens[userId] = token; - } - - public bool TryGetToken(string userId, out string token) - { - return _tokens.TryGetValue(userId, out token); - } - - public void RemoveToken(string userId) - { - _tokens.Remove(userId); - } - } - - public class Auth0TokenResponse - { - public string access_token { get; set; } - public string id_token { get; set; } - public string scope { get; set; } - public int expires_in { get; set; } - public string token_type { get; set; } - } - } -} diff --git a/samples/bot-auth0-adaptivecard/csharp/Controllers/BotController.cs b/samples/bot-auth0-adaptivecard/csharp/Controllers/BotController.cs deleted file mode 100644 index 35514d52ea..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/Controllers/BotController.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; - -namespace Microsoft.BotBuilderSamples.Controllers -{ - // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot - // implementation at runtime. Multiple different IBot implementations running at different endpoints can be - // achieved by specifying a more specific type for the bot constructor argument. - [Route("api/messages")] - [ApiController] - public class BotController : ControllerBase - { - private readonly IBotFrameworkHttpAdapter Adapter; - private readonly IBot Bot; - - public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) - { - Adapter = adapter; - Bot = bot; - } - - [HttpPost] - public async Task PostAsync() - { - // Delegate the processing of the HTTP POST to the adapter. - // The adapter will invoke the bot. - await Adapter.ProcessAsync(Request, Response, Bot); - } - } -} diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/.gitignore b/samples/bot-auth0-adaptivecard/csharp/M365Agent/.gitignore new file mode 100644 index 0000000000..84c2d9618f --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/.gitignore @@ -0,0 +1,9 @@ +# TeamsFx files +build +appPackage/build +env/.env.*.user +env/.env.local +.deployment + +# User-specific files +*.user diff --git a/samples/tab-conversations/csharp/M365Agent/M365Agent.ttkproj b/samples/bot-auth0-adaptivecard/csharp/M365Agent/M365Agent.atkproj similarity index 51% rename from samples/tab-conversations/csharp/M365Agent/M365Agent.ttkproj rename to samples/bot-auth0-adaptivecard/csharp/M365Agent/M365Agent.atkproj index 5cf470ae82..124eb75046 100644 --- a/samples/tab-conversations/csharp/M365Agent/M365Agent.ttkproj +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/M365Agent.atkproj @@ -1,12 +1,8 @@ - + - 696f2f0a-c3aa-4ff8-8d35-98a5f4ce4960 + b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf - - - - diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/color.png b/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/color.png new file mode 100644 index 0000000000..01aa37e347 Binary files /dev/null and b/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/color.png differ diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/icon-color.png b/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/icon-color.png deleted file mode 100644 index b8cf81afbe..0000000000 Binary files a/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/icon-color.png and /dev/null differ diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/icon-outline.png b/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/icon-outline.png deleted file mode 100644 index 2c3bf6fa65..0000000000 Binary files a/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/icon-outline.png and /dev/null differ diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/manifest.json b/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/manifest.json index d8bc41d716..29f7629ccd 100644 --- a/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/manifest.json +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/manifest.json @@ -1,6 +1,6 @@ { - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json", - "manifestVersion": "1.19", + "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.23/MicrosoftTeams.schema.json", + "manifestVersion": "1.23", "version": "1.0.0", "id": "${{TEAMS_APP_ID}}", "developer": { @@ -10,8 +10,8 @@ "termsOfUseUrl": "https://www.teams.com/termsofuser" }, "icons": { - "outline": "icon-outline.png", - "color": "icon-color.png" + "outline": "outline.png", + "color": "color.png" }, "name": { "short": "Auth0 Bot", @@ -24,7 +24,7 @@ "accentColor": "#FFFFFF", "bots": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "${{BOT_ID}}", "scopes": [ "personal", "groupChat", diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/outline.png b/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/outline.png new file mode 100644 index 0000000000..f7a4c86447 Binary files /dev/null and b/samples/bot-auth0-adaptivecard/csharp/M365Agent/appPackage/outline.png differ diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/env/.env.dev b/samples/bot-auth0-adaptivecard/csharp/M365Agent/env/.env.dev new file mode 100644 index 0000000000..df4f9da508 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/env/.env.dev @@ -0,0 +1,15 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/env/.env.local b/samples/bot-auth0-adaptivecard/csharp/M365Agent/env/.env.local index 215f2d3485..991fab7e14 100644 --- a/samples/bot-auth0-adaptivecard/csharp/M365Agent/env/.env.local +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/env/.env.local @@ -7,18 +7,10 @@ APP_NAME_SUFFIX=local # Generated during provision, you can also add your own variables. BOT_ID= TEAMS_APP_ID= -RESOURCE_SUFFIX= -AAD_APP_CLIENT_ID= -AAD_APP_OBJECT_ID= -AAD_APP_TENANT_ID= -AAD_APP_OAUTH_AUTHORITY= -AAD_APP_OAUTH_AUTHORITY_HOST= TEAMS_APP_TENANT_ID= -MICROSOFT_APP_TYPE= -MICROSOFT_APP_TENANT_ID= -AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP_NAME= +BOT_OBJECT_ID= + TEAMSFX_M365_USER_NAME= -BOT_ENDPOINT=https://cfrs8n65-7130.inc1.devtunnels.ms -BOT_DOMAIN=cfrs8n65-7130.inc1.devtunnels.ms \ No newline at end of file +BOT_ENDPOINT= +BOT_DOMAIN= \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/azure.bicep b/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/azure.bicep index c3ce051b3d..658e412a21 100644 --- a/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/azure.bicep +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/azure.bicep @@ -3,42 +3,84 @@ @description('Used to generate names for all resources in this file') param resourceBaseName string -@description('Required when create Azure Bot service') -param botAadAppClientId string - -param botAppDomain string +param webAppSKU string @maxLength(42) param botDisplayName string -param botServiceName string = resourceBaseName -param botServiceSku string = 'F0' -param microsoftAppType string -param microsoftAppTenantId string +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param identityName string = resourceBaseName +param location string = resourceGroup().location -// Register your web service as a bot with the Bot Framework -resource botService 'Microsoft.BotService/botServices@2021-03-01' = { - kind: 'azurebot' - location: 'global' - name: botServiceName - properties: { - displayName: botDisplayName - endpoint: 'https://${botAppDomain}/api/messages' - msaAppId: botAadAppClientId - msaAppType: microsoftAppType - msaAppTenantId: microsoftAppType == 'SingleTenant' ? microsoftAppTenantId : '' - } +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + location: location + name: identityName +} + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName sku: { - name: botServiceSku + name: webAppSKU } } -// Connect the bot service to Microsoft Teams -resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { - parent: botService - location: 'global' - name: 'MsTeamsChannel' +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName properties: { - channelName: 'MsTeamsChannel' + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' + } + { + name: 'Teams__ClientId' + value: identity.properties.clientId + } + { + name: 'Teams__TenantId' + value: identity.properties.tenantId + } + { + name: 'Teams__BotType' + value: 'UserAssignedMsi' + } + ] + ftpsState: 'FtpsOnly' + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${identity.id}': {} + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + identityClientId: identity.properties.clientId + identityResourceId: identity.id + identityTenantId: identity.properties.tenantId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName } } + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName +output BOT_ID string = identity.properties.clientId +output BOT_TENANT_ID string = identity.properties.tenantId diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/azure.parameters.json b/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/azure.parameters.json index d8454ee752..9a168936c3 100644 --- a/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/azure.parameters.json +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/azure.parameters.json @@ -1,24 +1,15 @@ { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "resourceBaseName": { - "value": "bot${{RESOURCE_SUFFIX}}" - }, - "botAadAppClientId": { - "value": "${{AAD_APP_CLIENT_ID}}" - }, - "botAppDomain": { - "value": "${{BOT_DOMAIN}}" - }, - "botDisplayName": { - "value": "TestBot" - }, - "microsoftAppType": { - "value": "${{MICROSOFT_APP_TYPE}}" - }, - "microsoftAppTenantId": { - "value": "${{MICROSOFT_APP_TENANT_ID}}" + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "TeamsAuth0Bot" + } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/botRegistration/azurebot.bicep b/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..a5a27b8fe4 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/botRegistration/azurebot.bicep @@ -0,0 +1,42 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param identityResourceId string +param identityClientId string +param identityTenantId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: identityClientId + msaAppMSIResourceId: identityResourceId + msaAppTenantId:identityTenantId + msaAppType:'UserAssignedMSI' + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/botRegistration/readme.md b/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/launchSettings.json b/samples/bot-auth0-adaptivecard/csharp/M365Agent/launchSettings.json index 6cd81bc8c2..2af8ce7a8a 100644 --- a/samples/bot-auth0-adaptivecard/csharp/M365Agent/launchSettings.json +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/launchSettings.json @@ -1,15 +1,25 @@ { "profiles": { - // Debug project within Teams + // Launch project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground (browser)": { + "commandName": "Project", + "environmentVariables": { + "UPDATE_TEAMS_APP": "false", + "M365_AGENTS_PLAYGROUND_TARGET_SDK": "teams-ai-v2-dotnet" + }, + "launchTestTool": true, + "launchUrl": "http://localhost:56150", + }, + // Launch project within Teams "Microsoft Teams (browser)": { "commandName": "Project", - "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" + "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}", }, // Launch project within Teams without prepare app dependencies "Microsoft Teams (browser) (skip update app)": { "commandName": "Project", "environmentVariables": { "UPDATE_TEAMS_APP": "false" }, "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" - } + }, } } \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/m365agents.local.yml b/samples/bot-auth0-adaptivecard/csharp/M365Agent/m365agents.local.yml index f61f2303dc..afa0ebbd7b 100644 --- a/samples/bot-auth0-adaptivecard/csharp/M365Agent/m365agents.local.yml +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/m365agents.local.yml @@ -1,80 +1,68 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.2 - -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:bot-auth0-adaptivecard-csharp +version: v1.11 provision: - - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty - with: - name: bot-auth0-adaptivecard-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. - generateClientSecret: true # If the value is false, the action will not generate client secret for you - signInAudience: "AzureADMultipleOrgs" # Multitenant - writeToEnvironmentFile: # Write the information of created resources into environment file for the specified environment variable(s). - clientId: AAD_APP_CLIENT_ID - clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file - objectId: AAD_APP_OBJECT_ID - tenantId: AAD_APP_TENANT_ID - authority: AAD_APP_OAUTH_AUTHORITY - authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST - # Creates a Teams app - uses: teamsApp/create with: # Teams app name - name: bot-auth0-adaptivecard${{APP_NAME_SUFFIX}} + name: TeamsAuth0Bot${{APP_NAME_SUFFIX}} # Write the information of created resources into environment file for # the specified environment variable(s). - writeToEnvironmentFile: + writeToEnvironmentFile: teamsAppId: TEAMS_APP_ID - - uses: script + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create with: - run: - echo "::set-teamsfx-env MICROSOFT_APP_TYPE=SingleTenant"; - echo "::set-teamsfx-env MICROSOFT_APP_TENANT_ID=${{AAD_APP_TENANT_ID}}"; + # The Microsoft Entra application's display name + name: TeamsAuth0Bot${{APP_NAME_SUFFIX}} + generateClientSecret: true + generateServicePrincipal: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID # Generate runtime appsettings to JSON file - uses: file/createOrUpdateJsonFile with: - target: ../appsettings.json + target: ../TeamsAuth0Bot/appsettings.Development.json content: - MicrosoftAppId: ${{AAD_APP_CLIENT_ID}} - MicrosoftAppPassword: ${{SECRET_AAD_APP_CLIENT_SECRET}} - MicrosoftAppType: ${{MICROSOFT_APP_TYPE}} - MicrosoftAppTenantId: ${{MICROSOFT_APP_TENANT_ID}} + Teams: + ClientId: ${{BOT_ID}} + ClientSecret: ${{SECRET_BOT_PASSWORD}} + TenantId: ${{TEAMS_APP_TENANT_ID}} ApplicationUrl: ${{BOT_ENDPOINT}} - - uses: arm/deploy # Deploy given ARM templates parallelly. + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create with: - subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} # The AZURE_SUBSCRIPTION_ID is a built-in environment variable. TeamsFx will ask you to select one subscription if its value is empty. You're free to reference other environment variable here, but TeamsFx will not ask you to select subscription if it's empty in this case. - resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} # The AZURE_RESOURCE_GROUP_NAME is a built-in environment variable. TeamsFx will ask you to select or create one resource group if its value is empty. You're free to reference other environment variable here, but TeamsFx will not ask you to select or create resource group if it's empty in this case. - templates: - - path: ./infra/azure.bicep - parameters: ./infra/azure.parameters.json - deploymentName: Create-resources-for-bot - bicepCliVersion: v0.9.1 # Teams Toolkit will download this bicep CLI version from github for you, will use bicep CLI in PATH if you remove this config. - - - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. - with: - manifestPath: ./aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app - outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json + botId: ${{BOT_ID}} + name: TeamsAuth0Bot + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams # Validate using manifest schema - uses: teamsApp/validateManifest with: # Path to manifest template manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: # Path to manifest template manifestPath: ./appPackage/manifest.json outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + outputFolder: ./appPackage/build # Validate app package using validation rules - uses: teamsApp/validateAppPackage with: @@ -82,9 +70,9 @@ provision: appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip # Apply the Teams app manifest to an existing Teams app in - # Teams Developer Portal. + # Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. - uses: teamsApp/update with: # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip \ No newline at end of file + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/m365agents.yml b/samples/bot-auth0-adaptivecard/csharp/M365Agent/m365agents.yml index ab50c85303..820f9819bc 100644 --- a/samples/bot-auth0-adaptivecard/csharp/M365Agent/m365agents.yml +++ b/samples/bot-auth0-adaptivecard/csharp/M365Agent/m365agents.yml @@ -1,9 +1,89 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.2 +version: v1.9 -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:bot-auth0-adaptivecard-csharp +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: TeamsAuth0Bot${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + - uses: cli/runDotnetCommand + with: + args: publish --configuration Release --runtime win-x86 --self-contained + TeamsAuth0Bot.csproj + workingDirectory: ../TeamsAuth0Bot + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: bin/Release/net10.0/win-x86/publish + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + workingDirectory: ../TeamsAuth0Bot -environmentFolderPath: ./env \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/Program.cs b/samples/bot-auth0-adaptivecard/csharp/Program.cs deleted file mode 100644 index 15de094baf..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/Program.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Microsoft.BotBuilderSamples -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.ConfigureLogging((logging) => - { - logging.AddDebug(); - logging.AddConsole(); - }); - webBuilder.UseStartup(); - }); - } -} diff --git a/samples/bot-auth0-adaptivecard/csharp/Properties/launchSettings.json b/samples/bot-auth0-adaptivecard/csharp/Properties/launchSettings.json deleted file mode 100644 index 3223ec1149..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/Properties/launchSettings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "profiles": { - "Start Project": { - "commandName": "Project", - "dotnetRunMessages": true, - "applicationUrl": "https://localhost:7130;http://localhost:5130", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "hotReloadProfile": "aspnetcore" - } - } -} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/README.md b/samples/bot-auth0-adaptivecard/csharp/README.md index 6612ae8263..552afbca22 100644 --- a/samples/bot-auth0-adaptivecard/csharp/README.md +++ b/samples/bot-auth0-adaptivecard/csharp/README.md @@ -57,16 +57,16 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool > Note these instructions are for running the sample on your local machine, the tunnelling solution is required because the Teams service needs to call into the bot. -1) Run ngrok - point to port 3978 +1) Run ngrok - point to port 5130 ```bash - ngrok http 3978 --host-header="localhost:3978" + ngrok http 5130 --host-header="localhost:5130" ``` Alternatively, you can also use the `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: ```bash - devtunnel host -p 3978 --allow-anonymous + devtunnel host -p 5130 --allow-anonymous ``` 1) Register a new application in the [Microsoft Entra ID – App Registrations](https://go.microsoft.com/fwlink/?linkid=2083908) portal. @@ -128,23 +128,27 @@ the Teams service needs to call into the bot. - Navigate to `samples/bot-auth0-adaptivecard/csharp` folder - Select `TeamsConversationBot.csproj` or `TeamsConversationBot.sln`file -1) Update the `appsettings.json` configuration for the bot to use the MicrosoftAppId, MicrosoftAppPassword, MicrosoftAppTenantId generated in Step 2 (App Registration creation). (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) - - Also, set MicrosoftAppType in the `appsettings.json`. (**Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI**) - - In addition, add your Auth0 configuration details: - ClientId: Found in your Auth0 application settings. - ClientSecret: Found in your Auth0 application settings. - Domain: Your Auth0 domain (e.g., your-tenant.auth0.com) +1) Update the `appsettings.Development.json` configuration for the bot to use the App Registration details generated in Step 2: + - Update the `Teams` section: + - `ClientId`: Application (client) ID from your Azure App Registration + - `ClientSecret`: Client secret from your Azure App Registration + - `TenantId`: Directory (tenant) ID from your Azure App Registration + - `BotType`: Set to `SingleTenant`, `MultiTenant`, or `UserAssignedMSI` + - Update the `Auth0` section: + - `ClientId`: Found in your Auth0 application settings + - `ClientSecret`: Found in your Auth0 application settings + - `Domain`: Your Auth0 domain (e.g., your-tenant.auth0.com) + - Set `ApplicationUrl`: Your bot's public URL from ngrok or dev tunnel (e.g., https://1234.ngrok-free.app) 1) Run your bot, either from Visual Studio with `F5` or using `dotnet run` in the appropriate folder. 1) __*This step is specific to Teams.*__ - - **Edit** the `manifest.json` contained in the `appPackage` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Edit** the `manifest.json` contained in the `appPackage` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `{{TEAMS_APP_ID}}` and `{{BOT_ID}}` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) - **Edit** the `manifest.json` for `validDomains`, replace `<>` with base Url domain. E.g. if you are using ngrok it would be `https://1234.ngrok-free.app` then your domain-name will be `1234.ngrok-free.app` and if you are using dev tunnels then your domain will be like: `12345.devtunnels.ms`. - **Zip** up the contents of the `appPackage` folder to create a `appPackage.zip` (Make sure that zip file does not contain any subfolder otherwise you will get error while uploading your .zip package) - **Upload** the `appPackage.zip` to Teams (In Teams Apps/Manage your apps click "Upload an app". Browse to and Open the .zip file. At the next dialog, click the Add button.) - Add the app to personal/team/groupChat scope (Supported scopes) -**Note**: If you are facing any issue in your app, please uncomment [this](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/bot-auth0-adaptivecard/csharp/AdapterWithErrorHandler.cs#L25) line and put your debugger for local debug. ## Running the sample @@ -165,7 +169,7 @@ To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](htt ## Further reading -- [Bot Framework Documentation](https://docs.botframework.com) +- [Azure AI Bot Service Documentation](https://docs.botframework.com) - [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) - [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) - [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/bot-auth0-adaptivecard/csharp/Startup.cs b/samples/bot-auth0-adaptivecard/csharp/Startup.cs deleted file mode 100644 index 44f346b1e4..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/Startup.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.BotBuilderSamples.Bots; -using Microsoft.Extensions.Hosting; -using Microsoft.Bot.Connector.Authentication; -using static Microsoft.BotBuilderSamples.Controllers.AuthController; - -namespace Microsoft.BotBuilderSamples -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddHttpClient().AddControllers().AddNewtonsoftJson(options => - { - options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth; - }); - - // Create the Bot Framework Authentication to be used with the Bot Adapter. - services.AddSingleton(); - - services.AddSingleton(); - - // Create the Bot Adapter with error handling enabled. - services.AddSingleton(); - - // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. - services.AddTransient(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseDefaultFiles() - .UseStaticFiles() - .UseRouting() - .UseAuthorization() - .UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - - // app.UseHttpsRedirection(); - } - } -} diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.csproj b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.csproj deleted file mode 100644 index d2e6e5e695..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.csproj +++ /dev/null @@ -1,85 +0,0 @@ - - - - net6.0 - latest - - - - - - - - - - - - - - - - - - - - Always - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.sln b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.sln deleted file mode 100644 index 86b55dd239..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.3.32901.215 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamsAuth0Bot", "TeamsAuth0Bot.csproj", "{76D60C04-94EB-4042-B141-C2C0FACBDE94}" -EndProject -Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{42A0237C-30E0-42AE-B6ED-50F318517E77}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A69BE64C-7866-4F9A-99AC-3D7B6A3BA20D}" - ProjectSection(SolutionItems) = preProject - TeamsAuth0Bot.slnLaunch.user = TeamsAuth0Bot.slnLaunch.user - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {76D60C04-94EB-4042-B141-C2C0FACBDE94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76D60C04-94EB-4042-B141-C2C0FACBDE94}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76D60C04-94EB-4042-B141-C2C0FACBDE94}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76D60C04-94EB-4042-B141-C2C0FACBDE94}.Release|Any CPU.Build.0 = Release|Any CPU - {42A0237C-30E0-42AE-B6ED-50F318517E77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {42A0237C-30E0-42AE-B6ED-50F318517E77}.Debug|Any CPU.Build.0 = Debug|Any CPU - {42A0237C-30E0-42AE-B6ED-50F318517E77}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {42A0237C-30E0-42AE-B6ED-50F318517E77}.Release|Any CPU.ActiveCfg = Release|Any CPU - {42A0237C-30E0-42AE-B6ED-50F318517E77}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {29EE0BD1-BA0E-4D58-9F98-023E02D8B72E} - EndGlobalSection -EndGlobal diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.slnLaunch.user b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.slnLaunch.user index f3b400adce..de2a678867 100644 --- a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.slnLaunch.user +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.slnLaunch.user @@ -1,31 +1,52 @@ [ { - "Name": "Microsoft Teams (browser)", + "Name": "Microsoft 365 Agents Playground (browser)", "Projects": [ { - "Path": "TeamsAuth0Bot.csproj", - "Action": "Start", - "DebugTarget": "Start Project" + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft 365 Agents Playground (browser)" }, { - "Path": "M365Agent\\M365Agent.ttkproj", - "Action": "StartWithoutDebugging", - "DebugTarget": "Microsoft Teams (browser)" + "Path": "TeamsAuth0Bot\\TeamsAuth0Bot.csproj", + "Name": "TeamsAuth0Bot\\TeamsAuth0Bot.csproj", + "Action": "Start", + "DebugTarget": "Microsoft 365 Agents Playground" } ] }, { - "Name": "Microsoft Teams (browser) (skip update app)", + "Name": "Microsoft Teams (browser)", "Projects": [ { - "Path": "TeamsAuth0Bot.csproj", + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft Teams (browser)" + }, + { + "Path": "TeamsAuth0Bot\\TeamsAuth0Bot.csproj", + "Name": "TeamsAuth0Bot\\TeamsAuth0Bot.csproj", "Action": "Start", "DebugTarget": "Start Project" - }, + } + ] + }, + { + "Name": "Microsoft Teams (browser) (skip update app)", + "Projects": [ { - "Path": "M365Agent\\M365Agent.ttkproj", + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", "Action": "StartWithoutDebugging", "DebugTarget": "Microsoft Teams (browser) (skip update app)" + }, + { + "Path": "TeamsAuth0Bot\\TeamsAuth0Bot.csproj", + "Name": "TeamsAuth0Bot\\TeamsAuth0Bot.csproj", + "Action": "Start", + "DebugTarget": "Start Project" } ] } diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.slnx b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.slnx new file mode 100644 index 0000000000..20e689bbed --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/samples/bot-auth0-adaptivecard/csharp/.gitignore b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/.gitignore similarity index 70% rename from samples/bot-auth0-adaptivecard/csharp/.gitignore rename to samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/.gitignore index 7466c01f9c..77c7154916 100644 --- a/samples/bot-auth0-adaptivecard/csharp/.gitignore +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/.gitignore @@ -5,6 +5,7 @@ env/.env.*.user env/.env.local appsettings.Development.json .deployment +appsettings.Playground.json # User-specific files *.user @@ -22,4 +23,8 @@ bld/ [Ll]og/ # Notification local store -.notification.localstore.json \ No newline at end of file +.notification.localstore.json +.notification.playgroundstore.json + +# devTools +devTools/ \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Config.cs b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Config.cs new file mode 100644 index 0000000000..3a68e359dd --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Config.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace TeamsAuth0Bot +{ + public class ConfigOptions + { + public TeamsConfigOptions Teams { get; set; } + public Auth0ConfigOptions Auth0 { get; set; } + public string ApplicationUrl { get; set; } + } + + public class TeamsConfigOptions + { + public string BotType { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public string TenantId { get; set; } + } + + public class Auth0ConfigOptions + { + public string Domain { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + } +} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Controllers/AuthController.cs b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Controllers/AuthController.cs new file mode 100644 index 0000000000..f419b88daf --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Controllers/AuthController.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; +using System.Web; +using TeamsAuth0Bot.Services; + +namespace TeamsAuth0Bot.Controllers +{ + [Route("api/auth")] + [ApiController] + public class AuthController : ControllerBase + { + private readonly ConfigOptions _config; + private readonly IHttpClientFactory _clientFactory; + + public AuthController(ConfigOptions config, IHttpClientFactory clientFactory) + { + _config = config; + _clientFactory = clientFactory; + } + + // Handles Auth0 OAuth callback, exchanges authorization code for access token, and stores it + [HttpGet("callback")] + public async Task Callback( + [FromQuery] string code, + [FromQuery] string state, + [FromServices] TokenStore tokenStore) + { + var client = _clientFactory.CreateClient(); + var tokenEndpoint = $"https://{_config.Auth0.Domain}/oauth/token"; + + var response = await client.PostAsync(tokenEndpoint, new FormUrlEncodedContent(new Dictionary + { + {"grant_type", "authorization_code"}, + {"client_id", _config.Auth0.ClientId}, + {"client_secret", _config.Auth0.ClientSecret}, + {"code", code}, + {"redirect_uri", _config.ApplicationUrl + "/api/auth/callback"} + })); + + if (!response.IsSuccessStatusCode) + { + return BadRequest("Token exchange failed"); + } + + var content = await response.Content.ReadAsStringAsync(); + var tokenResult = JsonSerializer.Deserialize(content); + + tokenStore.SaveToken(state, tokenResult!.access_token); + + return Redirect($"/auth-end.html?token={HttpUtility.UrlEncode(content)}"); + } + + public class Auth0TokenResponse + { + public string access_token { get; set; } = string.Empty; + public string id_token { get; set; } = string.Empty; + public string scope { get; set; } = string.Empty; + public int expires_in { get; set; } + public string token_type { get; set; } = string.Empty; + } + } +} diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Controllers/Controller.cs b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Controllers/Controller.cs new file mode 100644 index 0000000000..a37bb7d7e3 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Controllers/Controller.cs @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Teams.Api.Activities; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Activities; +using Microsoft.Teams.Apps.Annotations; +using Microsoft.Teams.Cards; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Web; +using TeamsAuth0Bot.Services; + +namespace TeamsAuth0Bot.Controllers +{ + [TeamsController] + public class Controller + { + private readonly ConfigOptions _config; + private readonly TokenStore _authToken; + private readonly IHttpClientFactory _clientFactory; + + public Controller(ConfigOptions config, TokenStore tokenStore, IHttpClientFactory clientFactory) + { + _config = config; + _authToken = tokenStore; + _clientFactory = clientFactory; + } + + // Handles incoming messages, manages authentication, and displays user profile or login card + [Message] + public async Task OnMessage([Context] MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + var userId = activity.From.Id; + var text = activity.Text?.Trim().ToLowerInvariant(); + + log.Info($"[CONFIG] Teams ClientId: {_config.Teams?.ClientId ?? "NULL"}"); + log.Info($"[CONFIG] Auth0 Domain: {_config.Auth0?.Domain ?? "NULL"}"); + + if (text == "logout") + { + _authToken.RemoveToken(userId); + + var logoutUrl = $"https://{_config.Auth0.Domain}/v2/logout?client_id={_config.Auth0.ClientId}"; + + var card = CreateHeroCard("You've been logged out.", "Logout from Auth0", logoutUrl); + + await client.Send(card); + return; + } + + if (_authToken.TryGetToken(userId, out var accessToken)) + { + if (text == "profile details") + { + var httpClient = _clientFactory.CreateClient(); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var userInfoEndpoint = $"https://{_config.Auth0.Domain}/userinfo"; + var response = await httpClient.GetAsync(userInfoEndpoint); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + var profileData = JsonSerializer.Deserialize>(content); + + var card = new AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("Auth0 Profile") + { + Size = TextSize.Large, + Weight = TextWeight.Bolder, + Wrap = true + }, + new Image(profileData["picture"].GetString()) + { + Size = Size.Medium, + Style = ImageStyle.Person + }, + new TextBlock($"Name: {profileData["name"].GetString()}") + { + Wrap = true + }, + new TextBlock($"Email: {profileData["email"].GetString()}") + { + Wrap = true + } + } + }; + + await client.Send(card); + await client.Send($"Your profile Data: {JsonSerializer.Serialize(profileData)}"); + } + else + { + await client.Send("Failed to fetch profile details."); + } + } + else + { + await client.Send("Say 'profile details' to get your Auth0 profile or 'logout' to log out."); + } + } + else + { + var loginUrl = GenerateLoginUrl(userId); + + var card = CreateHeroCard("Login Required", "Login", loginUrl); + + await client.Send(card); + } + } + + // Welcomes new members when they are added to the conversation + [Conversation.MembersAdded] + public async Task OnMembersAdded(IContext context) + { + var welcomeText = "Type anything to get a login card"; + foreach (var member in context.Activity.MembersAdded) + { + if (member.Id != context.Activity.Recipient.Id) + { + await context.Send(welcomeText); + } + } + } + + // Generates Auth0 authorization URL with callback and user state + private string GenerateLoginUrl(string userId) + { + var domain = _config.Auth0.Domain; + if (!domain.StartsWith("https://")) + { + domain = "https://" + domain; + } + + var authUrl = $"{domain}/authorize" + + $"?response_type=code&client_id={_config.Auth0.ClientId}" + + $"&redirect_uri={_config.ApplicationUrl}/api/auth/callback" + + $"&scope=openid profile email" + + $"&state={HttpUtility.UrlEncode(userId)}"; + + return authUrl; + } + + // Creates an adaptive card with a title and action button + private static AdaptiveCard CreateHeroCard(string title, string buttonTitle, string buttonUrl) + { + return new AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock(title) + { + Size = TextSize.Large, + Weight = TextWeight.Bolder, + Wrap = true + } + }, + Actions = new List + { + new OpenUrlAction(buttonUrl) + { + Title = buttonTitle + } + } + }; + } + + // Removes carriage return and newline characters for safe logging + private static string SanitizeForLog(string input) + { + if (input == null) return ""; + return input.Replace("\r", "").Replace("\n", ""); + } + } +} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Program.cs b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Program.cs new file mode 100644 index 0000000000..9bec6e6c84 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Program.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using TeamsAuth0Bot; +using TeamsAuth0Bot.Controllers; +using TeamsAuth0Bot.Services; +using Microsoft.Teams.Apps.Extensions; +using Microsoft.Teams.Plugins.AspNetCore.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +// Bind configuration to ConfigOptions once and register it +var configOptions = new ConfigOptions +{ + Teams = builder.Configuration.GetSection("Teams").Get() ?? new TeamsConfigOptions(), + Auth0 = builder.Configuration.GetSection("Auth0").Get() ?? new Auth0ConfigOptions(), + ApplicationUrl = builder.Configuration.GetValue("ApplicationUrl") ?? string.Empty +}; +builder.Services.AddSingleton(configOptions); + +// Services +builder.Services.AddHttpClient(); +builder.Services.AddControllers(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +// Teams SDK +builder.AddTeams(); + +var app = builder.Build(); + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.UseRouting(); +app.UseAuthorization(); + +app.MapControllers(); + +app.UseTeams(); +app.Run(); \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Properties/launchSettings.json b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Properties/launchSettings.json new file mode 100644 index 0000000000..3572a7a03f --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Properties/launchSettings.json @@ -0,0 +1,26 @@ +{ + "profiles": { + // Debug project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground": { + "commandName": "Project", + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Playground", + "TEAMSFX_NOTIFICATION_STORE_FILENAME": ".notification.playgroundstore.json", + "UPDATE_TEAMS_APP": "false" + }, + "hotReloadProfile": "aspnetcore" + }, + // Debug project within Teams + "Start Project": { + "commandName": "Project", + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "hotReloadProfile": "aspnetcore" + }, + } +} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Services/TokenStore.cs b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Services/TokenStore.cs new file mode 100644 index 0000000000..30ab011f07 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/Services/TokenStore.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace TeamsAuth0Bot.Services +{ + public class TokenStore + { + private readonly Dictionary _tokens = new(); + + public void SaveToken(string userId, string token) + { + _tokens[userId] = token; + } + + public bool TryGetToken(string userId, out string token) + { + return _tokens.TryGetValue(userId, out token); + } + + public void RemoveToken(string userId) + { + _tokens.Remove(userId); + } + } +} diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/TeamsAuth0Bot.csproj b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/TeamsAuth0Bot.csproj new file mode 100644 index 0000000000..ba6c041910 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/TeamsAuth0Bot.csproj @@ -0,0 +1,37 @@ + + + + net10.0 + enable + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + None + + + + PreserveNewest + None + + + \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/appsettings.Development.json b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/appsettings.Development.json new file mode 100644 index 0000000000..71ad738564 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/appsettings.Development.json @@ -0,0 +1,25 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "TenantId": "", + "BotType": "SingleTenant" + }, + "Auth0": { + "ClientId": "", + "ClientSecret": "", + "Domain": "" + }, + "ApplicationUrl": "" +} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/appsettings.Playground.json b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/appsettings.Playground.json new file mode 100644 index 0000000000..47b7c8e892 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/appsettings.Playground.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "TenantId": "", + "BotType": "" + } +} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/appsettings.json b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/appsettings.json new file mode 100644 index 0000000000..964462147e --- /dev/null +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/appsettings.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "" + }, + "ApplicationUrl": "" +} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/wwwroot/auth-end.html b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/wwwroot/auth-end.html similarity index 95% rename from samples/bot-auth0-adaptivecard/csharp/wwwroot/auth-end.html rename to samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/wwwroot/auth-end.html index 31c08a8896..fc22930fda 100644 --- a/samples/bot-auth0-adaptivecard/csharp/wwwroot/auth-end.html +++ b/samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot/wwwroot/auth-end.html @@ -1,4 +1,4 @@ - + Authentication Complete @@ -17,4 +17,4 @@

Login Complete! You may now return to Teams.

- \ No newline at end of file + diff --git a/samples/bot-auth0-adaptivecard/csharp/appsettings.json b/samples/bot-auth0-adaptivecard/csharp/appsettings.json deleted file mode 100644 index 7babc70e47..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/appsettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "MicrosoftAppType": "MultiTenant", - "MicrosoftAppId": "", - "MicrosoftAppPassword": "", - "MicrosoftAppTenantId": "", - "ApplicationUrl": "", - "Auth0": { - "ClientId": "", - "ClientSecret": "", - "Domain": "" - } -} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/csharp/assets/sample.json b/samples/bot-auth0-adaptivecard/csharp/assets/sample.json index a4dde90355..02700a7a97 100644 --- a/samples/bot-auth0-adaptivecard/csharp/assets/sample.json +++ b/samples/bot-auth0-adaptivecard/csharp/assets/sample.json @@ -9,7 +9,7 @@ "This sample demonstrates how to authenticate users in a Microsoft Teams bot using Auth0 login and retrieve their profile details. After authentication, the bot displays the user's name, email, and profile picture in an Adaptive Card" ], "creationDateTime": "2025-04-30", - "updateDateTime": "2025-04-30", + "updateDateTime": "2025-10-12", "products": [ "Teams" ], diff --git a/samples/bot-auth0-adaptivecard/csharp/wwwroot/default.html b/samples/bot-auth0-adaptivecard/csharp/wwwroot/default.html deleted file mode 100644 index 8a0234211f..0000000000 --- a/samples/bot-auth0-adaptivecard/csharp/wwwroot/default.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - - Teams Conversation Bot - - - - - -
-
-
-
Conversation Bot
-
-
-
-
-
Your bot is ready!
-
- You can now test your bot in Teams.
-
- Visit - Azure - Bot Service - to register your bot and add it to the
- Teams channel. The bot's endpoint URL typically looks - like this: -
-
https://your_bots_hostname/api/messages
-
-
-
- -
- - - \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/.env b/samples/bot-auth0-adaptivecard/python/.env index d2c8620ee9..653740e6e3 100644 --- a/samples/bot-auth0-adaptivecard/python/.env +++ b/samples/bot-auth0-adaptivecard/python/.env @@ -1,6 +1,8 @@ -MicrosoftAppId= -MicrosoftAppPassword= -BOT_ENDPOINT= +PORT=3978 +CLIENT_ID= +CLIENT_SECRET= +TENANT_ID= AUTH0_DOMAIN= AUTH0_CLIENT_ID= -AUTH0_CLIENT_SECRET= \ No newline at end of file +AUTH0_CLIENT_SECRET= +BOT_ENDPOINT= \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/.gitignore b/samples/bot-auth0-adaptivecard/python/.gitignore index e8442994dd..b360220001 100644 --- a/samples/bot-auth0-adaptivecard/python/.gitignore +++ b/samples/bot-auth0-adaptivecard/python/.gitignore @@ -1,14 +1,18 @@ # TeamsFx files env/.env.*.user env/.env.local -appManifest/build/ +env/.env.playground +.env +appPackage/build # python virtual environment .venv/ +__pycache__/ -# misc -.env +# others .deployment/ +node_modules/ +devTools/*.log -# tmp files -__pycache__/ \ No newline at end of file +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/.vscode/extensions.json b/samples/bot-auth0-adaptivecard/python/.vscode/extensions.json index bf8c33db9c..760a0b1d8f 100644 --- a/samples/bot-auth0-adaptivecard/python/.vscode/extensions.json +++ b/samples/bot-auth0-adaptivecard/python/.vscode/extensions.json @@ -1,6 +1,6 @@ { - "recommendations": [ - "TeamsDevApp.ms-teams-vscode-extension", - "ms-python.python", - ] + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension", + "ms-python.python" + ] } \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/.vscode/launch.json b/samples/bot-auth0-adaptivecard/python/.vscode/launch.json index 6d66d8beb8..acebe10dcf 100644 --- a/samples/bot-auth0-adaptivecard/python/.vscode/launch.json +++ b/samples/bot-auth0-adaptivecard/python/.vscode/launch.json @@ -1,69 +1,133 @@ { - "version": "0.2.0", - "configurations": [ - { - "name": "Launch App (Edge)", - "type": "msedge", - "request": "launch", - "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", - "cascadeTerminateToConfigurations": [ - "Python: Run App Locally" - ], - "presentation": { - "group": "all", - "hidden": true - }, - "internalConsoleOptions": "neverOpen" + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "1-Teams", + "order": 4 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "1-Teams", + "order": 5 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Desktop)", + "type": "node", + "request": "launch", + "preLaunchTask": "Start App in Desktop Client (Remote)", + "presentation": { + "group": "1-Teams", + "order": 6 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Start App (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Start App (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Start Python", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/src/app.py", + "cwd": "${workspaceFolder}/src", + "console": "integratedTerminal" + }, + { + "name": "Start Microsoft 365 Agents Playground", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/devTools/playground/node_modules/@microsoft/m365agentsplayground/cli.js", + "args": [ + "start" + ], + "env": { + "PATH": "${workspaceFolder}/devTools/nodejs;${env:PATH}" }, - { - "name": "Launch App (Chrome)", - "type": "chrome", - "request": "launch", - "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", - "cascadeTerminateToConfigurations": [ - "Python: Run App Locally" - ], - "presentation": { - "group": "all", - "hidden": true - }, - "internalConsoleOptions": "neverOpen" + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Teams (Edge)", + "configurations": ["Start App (Edge)", "Start Python"], + "cascadeTerminateToConfigurations": ["Start Python"], + "preLaunchTask": "Start App Locally", + "presentation": { + "group": "1-Teams", + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Chrome)", + "configurations": ["Start App (Chrome)", "Start Python"], + "cascadeTerminateToConfigurations": ["Start Python"], + "preLaunchTask": "Start App Locally", + "presentation": { + "group": "1-Teams", + "order": 2 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Desktop)", + "configurations": ["Start Python"], + "preLaunchTask": "Start App in Desktop Client", + "presentation": { + "group": "1-Teams", + "order": 3 + }, + "stopAll": true + }, + { + "name": "Debug in Microsoft 365 Agents Playground", + "configurations": [ + "Start Python", + "Start Microsoft 365 Agents Playground" + ], + "cascadeTerminateToConfigurations": [ + "Start Microsoft 365 Agents Playground" + ], + "preLaunchTask": "Deploy (Microsoft 365 Agents Playground)", + "presentation": { + "group": "0-TestTool", + "order": 1 }, - { - "name": "Python: Run App Locally", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/app.py", - "cwd": "${workspaceFolder}", - "console": "integratedTerminal" - } - ], - "compounds": [ - { - "name": "Debug (Edge)", - "configurations": [ - "Launch App (Edge)", - "Python: Run App Locally" - ], - "preLaunchTask": "Prepare Teams App Resources", - "presentation": { - "group": "all", - "order": 1 - }, - "stopAll": true - }, - { - "name": "Debug (Chrome)", - "configurations": [ - "Launch App (Chrome)", - "Python: Run App Locally" - ], - "preLaunchTask": "Prepare Teams App Resources", - "presentation": { - "group": "all", - "order": 2 - }, - "stopAll": true - } - ] -} \ No newline at end of file + "stopAll": true + } + ] +} diff --git a/samples/bot-auth0-adaptivecard/python/.vscode/settings.json b/samples/bot-auth0-adaptivecard/python/.vscode/settings.json index 3014fd9cf0..0d3ba10b02 100644 --- a/samples/bot-auth0-adaptivecard/python/.vscode/settings.json +++ b/samples/bot-auth0-adaptivecard/python/.vscode/settings.json @@ -1,3 +1,11 @@ { - "debug.onTaskErrors": "abort" -} + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ] +} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/.vscode/tasks.json b/samples/bot-auth0-adaptivecard/python/.vscode/tasks.json index 253c1ead76..778f7beb7d 100644 --- a/samples/bot-auth0-adaptivecard/python/.vscode/tasks.json +++ b/samples/bot-auth0-adaptivecard/python/.vscode/tasks.json @@ -5,7 +5,118 @@ "version": "2.0.0", "tasks": [ { - "label": "Prepare Teams App Resources", + "label": "Start App (Sandbox)", + "dependsOn": [ + "Validate prerequisites (Sandbox)", + "Start local tunnel (Sandbox)", + "Provision (Sandbox)", + "Deploy (Sandbox)", + "Sideload App to channel" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites (Sandbox)", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "portOccupancy", + "sandbox", + "nodejs" + ], + "portOccupancy": [ + 3978 + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel (Sandbox)", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 3978, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT + "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN + } + } + ], + "env": "sandbox" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + // Create the debug resources. + // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. + "label": "Provision (Sandbox)", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "sandbox" + } + }, + { + // Build the project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy (Sandbox)", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "sandbox" + } + }, + { + "label": "Sideload App to channel", + "type": "teamsfx", + "command": "install-app", + "args": { + "env": "sandbox", + "appPackagePath": "${workspaceFolder}/appPackage/build/appPackage.sandbox.zip" + } + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites (Microsoft 365 Agents Playground)", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Check if Node.js is installed and the version is >= 12. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 56150, // Microsoft 365 Agents Playground port + ] + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy (Microsoft 365 Agents Playground)", + "dependsOn": [ + "Validate prerequisites (Microsoft 365 Agents Playground)" + ], + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "playground", + } + }, + { + "label": "Start App Locally", "dependsOn": [ "Validate prerequisites", "Start local tunnel", @@ -22,11 +133,11 @@ "command": "debug-check-prerequisites", "args": { "prerequisites": [ - "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the uploading permission. + "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. ], "portOccupancy": [ - 3978, // app service port + 3978 // app service port ] } }, @@ -73,6 +184,33 @@ "args": { "env": "local" } + }, + { + "label": "Start App in Desktop Client", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy", + "Start desktop client" + ], + "dependsOrder": "sequence" + }, + { + "label": "Start desktop client", + "type": "teamsfx", + "command": "launch-desktop-client", + "args": { + "url": "teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true" + } + }, + { + "label": "Start App in Desktop Client (Remote)", + "type": "teamsfx", + "command": "launch-desktop-client", + "args": { + "url": "teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true" + } } ] } \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/.webappignore b/samples/bot-auth0-adaptivecard/python/.webappignore new file mode 100644 index 0000000000..a93f2c5f65 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/python/.webappignore @@ -0,0 +1,10 @@ +.venv/ +.vscode/ +.env +env/ +__pycache__/ +README.md +m365agents.yml +m365agents.local.yml +m365agents.playground.yml +/devTools/ \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/README.md b/samples/bot-auth0-adaptivecard/python/README.md index 7f745733e1..9a29f796bb 100644 --- a/samples/bot-auth0-adaptivecard/python/README.md +++ b/samples/bot-auth0-adaptivecard/python/README.md @@ -81,6 +81,11 @@ the Teams service needs to call into the bot. 3) Create [Azure Bot resource resource](https://docs.microsoft.com/azure/bot-service/bot-service-quickstart-registration) in Azure - Use the current `https` URL you were given by running the tunneling application. Append with the path `/api/messages` used by this sample - Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) + - Navigate to **API Permissions** and add the following: + - Select **Add a permission** > **Microsoft Graph** > **Delegated permissions** + - Add **User.Read** (enabled by default) + - Click **Add permissions** + - Grant admin consent for the required permissions - __*If you don't have an Azure account*__ you can use this [Azure free account here](https://azure.microsoft.com/free/) 4) Setup Auth0 Application @@ -110,23 +115,27 @@ __*Configure Application Settings:*__ 7) Install dependencies by running ```pip install -r requirements.txt``` in the project folder. -8) Update the `config.py` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) - -9) Update the `.env` file to include your Auth0 configuration details: - AUTH0_CLIENT_ID: Found in your Auth0 application settings. - AUTH0_CLIENT_SECRET: Found in your Auth0 application settings. - AUTH0_DOMAIN: Your Auth0 domain (e.g., your-tenant.auth0.com) +8) Update the `.env` file to include your bot configuration and Auth0 details: + ``` + CLIENT_ID= + CLIENT_SECRET= + TENANT_ID= + BOT_ENDPOINT= + AUTH0_CLIENT_ID= + AUTH0_CLIENT_SECRET= + AUTH0_DOMAIN= + ``` -10) Run your app with `python app.py` +9) Run your app with `python app.py` ### 4. Setup Manifest for Teams - **This step is specific to Teams.** - - **Edit** the `manifest.json` contained in the `bot-auth0-adaptivecard/python/appManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Edit** the `manifest.json` contained in the `bot-auth0-adaptivecard/python/appPackage` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `${{BOT_ID}}` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) - **Edit** the `manifest.json` for `configurationUrl` inside `configurableTabs` and `validDomains`. Replace `{{domain-name}}` with base Url domain. E.g. if you are using ngrok it would be `https://1234.ngrok-free.app` then your domain-name will be `1234.ngrok-free.app` and if you are using dev tunnels then your domain will be like: `12345.devtunnels.ms`. - - **Zip** up the contents of the `bot-auth0-adaptivecard/python/appManifest` folder to create a `manifest.zip`(Make sure that zip file does not contains any subfolder otherwise you will get error while uploading your .zip package) + - **Zip** up the contents of the `bot-auth0-adaptivecard/python/appPackage` folder to create a `manifest.zip`(Make sure that zip file does not contains any subfolder otherwise you will get error while uploading your .zip package) - **Upload** the `manifest.zip` to Teams (In Teams Apps/Manage your apps click "Upload an app". Browse to and Open the .zip file. At the next dialog, click the Add button.) - Add the app to personal/team/groupChat scope (Supported scopes) @@ -149,7 +158,7 @@ To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](htt ## Further reading -- [Bot Framework Documentation](https://docs.botframework.com) +- [Teams SDK](https://learn.microsoft.com/microsoftteams/platform/bots/how-to/teams%20conversational%20ai/teams-conversation-ai-overview) - [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) - [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) - [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/bot-auth0-adaptivecard/python/aad.manifest.json b/samples/bot-auth0-adaptivecard/python/aad.manifest.json new file mode 100644 index 0000000000..15bdaca181 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/python/aad.manifest.json @@ -0,0 +1,100 @@ +{ + "id": "${{AAD_APP_OBJECT_ID}}", + "appId": "${{AAD_APP_CLIENT_ID}}", + "displayName": "bot-auth0-adaptivecard-aad", + "signInAudience": "AzureADMyOrg", + "api": { + "requestedAccessTokenVersion": 2, + "oauth2PermissionScopes": [ + { + "adminConsentDescription": "Allows Teams to call the app's web APIs as the current user.", + "adminConsentDisplayName": "Teams can access app's web APIs", + "id": "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}", + "isEnabled": true, + "type": "User", + "userConsentDescription": "Enable Teams to call this app's web APIs with the same rights that you have", + "userConsentDisplayName": "Teams can access app's web APIs and make requests on your behalf", + "value": "access_as_user" + } + ], + "preAuthorizedApplications": [ + { + "appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264", + "delegatedPermissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346", + "delegatedPermissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c", + "delegatedPermissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "00000002-0000-0ff1-ce00-000000000000", + "delegatedPermissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "bc59ab01-8403-45c6-8796-ac3ef710b3e3", + "delegatedPermissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "0ec893e0-5785-4de6-99da-4ed124e5296c", + "delegatedPermissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4765445b-32c6-49b0-83e6-1d93765276ca", + "delegatedPermissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4345a7b9-9a63-4910-a426-35363201d503", + "delegatedPermissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + } + ] + }, + "info": {}, + "optionalClaims": { + "idToken": [], + "accessToken": [ + { + "name": "idtyp", + "source": null, + "essential": false, + "additionalProperties": [] + } + ], + "saml2Token": [] + }, + "publicClient": {}, + "requiredResourceAccess": [ + { + "resourceAppId": "Microsoft Graph", + "resourceAccess": [ + { + "id": "User.Read", + "type": "Scope" + } + ] + } + ], + "web": { + "implicitGrantSettings": {} + }, + "spa": {} +} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/app.py b/samples/bot-auth0-adaptivecard/python/app.py deleted file mode 100644 index 0c92d14f36..0000000000 --- a/samples/bot-auth0-adaptivecard/python/app.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 - -import uvicorn -from fastapi import FastAPI, Request, Response -from fastapi.responses import HTMLResponse -from fastapi.staticfiles import StaticFiles -from botbuilder.core import ( - BotFrameworkAdapterSettings, - BotFrameworkAdapter, - TurnContext, -) -from botbuilder.schema import Activity -from config import DefaultConfig -from bots.teams_conversation_bot import TeamsConversationBot -from controllers.auth_routes import router as auth_router -import json -import asyncio - -app = FastAPI() - -# Configuration -CONFIG = DefaultConfig() - -# Adapter setup -adapter_settings = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD) -adapter = BotFrameworkAdapter(adapter_settings) - -# Catch-all error handler -async def on_error(context: TurnContext, error: Exception): - print(f"[on_turn_error] unhandled error: {error}") - await context.send_activity("The bot encountered an error or bug.") - await context.send_activity("To continue to run this bot, please fix the bot source code.") - -adapter.on_turn_error = on_error - -# Bot instance -bot = TeamsConversationBot() - -# Root page -@app.get("/", response_class=HTMLResponse) -async def index(): - return """ - - Teams Bot + Auth0 - -

Teams Bot + Auth0

-

This bot is running. You can interact with it in Microsoft Teams.

- - - """ - -# Bot message endpoint -@app.post("/api/messages") -async def messages(request: Request): - body = await request.json() - activity = Activity().deserialize(body) - auth_header = request.headers.get("Authorization", "") - - async def call_bot(turn_context: TurnContext): - await bot.on_turn(turn_context) - - await adapter.process_activity(activity, auth_header, call_bot) - return Response(status_code=200) - -# Auth callback route -app.include_router(auth_router, prefix="/api/auth") -app.mount("/src/views", StaticFiles(directory="src/views"), name="static") - -# Run app -if __name__ == "__main__": - # Use the correct module name (replace `app` with your filename if different) - uvicorn.run("app:app", host="0.0.0.0", port=CONFIG.PORT, reload=True) \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/appManifest/color.png b/samples/bot-auth0-adaptivecard/python/appManifest/color.png deleted file mode 100644 index b8cf81afbe..0000000000 Binary files a/samples/bot-auth0-adaptivecard/python/appManifest/color.png and /dev/null differ diff --git a/samples/bot-auth0-adaptivecard/python/appManifest/outline.png b/samples/bot-auth0-adaptivecard/python/appManifest/outline.png deleted file mode 100644 index 2c3bf6fa65..0000000000 Binary files a/samples/bot-auth0-adaptivecard/python/appManifest/outline.png and /dev/null differ diff --git a/samples/bot-auth0-adaptivecard/python/appPackage/color.png b/samples/bot-auth0-adaptivecard/python/appPackage/color.png new file mode 100644 index 0000000000..01aa37e347 Binary files /dev/null and b/samples/bot-auth0-adaptivecard/python/appPackage/color.png differ diff --git a/samples/bot-auth0-adaptivecard/python/appManifest/manifest.json b/samples/bot-auth0-adaptivecard/python/appPackage/manifest.json similarity index 73% rename from samples/bot-auth0-adaptivecard/python/appManifest/manifest.json rename to samples/bot-auth0-adaptivecard/python/appPackage/manifest.json index 36c547fc38..f2383547e4 100644 --- a/samples/bot-auth0-adaptivecard/python/appManifest/manifest.json +++ b/samples/bot-auth0-adaptivecard/python/appPackage/manifest.json @@ -1,30 +1,30 @@ { - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json", - "manifestVersion": "1.19", + "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.23/MicrosoftTeams.schema.json", + "manifestVersion": "1.23", "version": "1.0.0", "id": "${{TEAMS_APP_ID}}", "developer": { "name": "Auth0 Bot", "websiteUrl": "https://www.microsoft.com", - "privacyUrl": "https://www.teams.com/privacy", - "termsOfUseUrl": "https://www.teams.com/termsofuser" - }, - "icons": { - "outline": "outline.png", - "color": "color.png" + "privacyUrl": "https://www.microsoft.com/privacy", + "termsOfUseUrl": "https://www.microsoft.com/termsofuse" }, "name": { "short": "Auth0 Bot", "full": "Auth0 Bot" }, "description": { - "short": "Demo Auth0 login sample in a Teams bot for user authentication", + "short": "Demo Auth0 login sample in a Teams bot", "full": "This sample demonstrates how to authenticate users in a Microsoft Teams bot using Auth0 login and retrieve their profile details." }, - "accentColor": "#FFFFFF", + "icons": { + "outline": "outline.png", + "color": "color.png" + }, + "accentColor": "#60A18E", "bots": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "${{BOT_ID}}", "scopes": [ "personal", "groupChat", diff --git a/samples/bot-auth0-adaptivecard/python/appPackage/outline.png b/samples/bot-auth0-adaptivecard/python/appPackage/outline.png new file mode 100644 index 0000000000..f7a4c86447 Binary files /dev/null and b/samples/bot-auth0-adaptivecard/python/appPackage/outline.png differ diff --git a/samples/bot-auth0-adaptivecard/python/bots/__init__.py b/samples/bot-auth0-adaptivecard/python/bots/__init__.py deleted file mode 100644 index ae6aca930d..0000000000 --- a/samples/bot-auth0-adaptivecard/python/bots/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .teams_conversation_bot import TeamsConversationBot - -__all__ = ["TeamsConversationBot"] \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/bots/teams_conversation_bot.py b/samples/bot-auth0-adaptivecard/python/bots/teams_conversation_bot.py deleted file mode 100644 index 4fd3a27d2c..0000000000 --- a/samples/bot-auth0-adaptivecard/python/bots/teams_conversation_bot.py +++ /dev/null @@ -1,142 +0,0 @@ -import os -import json -import requests -from botbuilder.core import ActivityHandler, MessageFactory, CardFactory, TurnContext -from services.token_store import TokenStore -from config import DefaultConfig - -CONFIG = DefaultConfig() - -class TeamsConversationBot(ActivityHandler): - async def on_message_activity(self, turn_context: TurnContext): - user_id = turn_context.activity.from_property.id - text = (turn_context.activity.text or "").strip().lower() - - token_store = TokenStore() # Get the singleton instance - - if text == "logout": - token_store.remove_token(user_id) - logout_url = ( - f"https://{CONFIG.AUTH0_DOMAIN}/v2/logout" - f"?client_id={CONFIG.AUTH0_CLIENT_ID}" - ) - - logout_card = { - "type": "AdaptiveCard", - "version": "1.3", - "body": [ - { - "type": "TextBlock", - "text": "You've been logged out.", - "size": "Medium", - "weight": "Bolder", - "wrap": True, - } - ], - "actions": [ - { - "type": "Action.OpenUrl", - "title": "Logout from Auth0", - "url": logout_url, - } - ], - } - - await turn_context.send_activity( - MessageFactory.attachment(CardFactory.adaptive_card(logout_card)) - ) - return - - access_token = token_store.get_token(user_id) - - if access_token: - if text == "profile details": - try: - response = requests.get( - f"https://{CONFIG.AUTH0_DOMAIN}/userinfo", - headers={"Authorization": f"Bearer {access_token}"}, - ) - - if response.status_code == 200: - profile_data = response.json() - profile_card = { - "type": "AdaptiveCard", - "version": "1.3", - "body": [ - { - "type": "TextBlock", - "text": "Auth0 Profile", - "size": "Large", - "weight": "Bolder", - "wrap": True, - }, - { - "type": "Image", - "url": profile_data.get("picture", "https://via.placeholder.com/150"), - "size": "Medium", - "style": "Person", - }, - { - "type": "TextBlock", - "text": f"Name: {profile_data.get('name')}", - "wrap": True, - }, - { - "type": "TextBlock", - "text": f"Email: {profile_data.get('email')}", - "wrap": True, - }, - ], - } - - await turn_context.send_activity( - MessageFactory.attachment(CardFactory.adaptive_card(profile_card)) - ) - else: - await turn_context.send_activity( - MessageFactory.text("Failed to fetch profile details.") - ) - except Exception as e: - print(f"Error fetching profile: {e}") - await turn_context.send_activity( - MessageFactory.text("Error retrieving profile details.") - ) - else: - await turn_context.send_activity( - MessageFactory.text("Say 'profile details' to get your profile or 'logout' to log out.") - ) - else: - login_url = self.generate_login_url(user_id) - login_card = { - "type": "AdaptiveCard", - "version": "1.3", - "body": [ - { - "type": "TextBlock", - "text": "Login Required", - "size": "Medium", - "weight": "Bolder", - "wrap": True, - } - ], - "actions": [ - { - "type": "Action.OpenUrl", - "title": "Login", - "url": login_url, - } - ], - } - - await turn_context.send_activity( - MessageFactory.attachment(CardFactory.adaptive_card(login_card)) - ) - - def generate_login_url(self, user_id: str) -> str: - return ( - f"https://{CONFIG.AUTH0_DOMAIN}/authorize" - f"?response_type=code&client_id={CONFIG.AUTH0_CLIENT_ID}" - f"&redirect_uri={CONFIG.BOT_ENDPOINT}/api/auth/callback" - f"&scope=openid profile email" - f"&state={user_id}" - ) \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/config.py b/samples/bot-auth0-adaptivecard/python/config.py deleted file mode 100644 index 972825fad9..0000000000 --- a/samples/bot-auth0-adaptivecard/python/config.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import os - -""" Bot + Auth Configuration """ - - -class DefaultConfig: - """Configuration for Bot Framework and Auth0""" - - PORT = 3978 - APP_ID = os.environ.get("MicrosoftAppId", "<>") - APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "<>") - AUTH0_DOMAIN = os.environ.get("AUTH0_DOMAIN", "<>") - AUTH0_CLIENT_ID = os.environ.get("AUTH0_CLIENT_ID", "<>") - AUTH0_CLIENT_SECRET = os.environ.get("AUTH0_CLIENT_SECRET", "<>") - BOT_ENDPOINT = os.environ.get("BOT_ENDPOINT", "http://localhost:3978") diff --git a/samples/bot-auth0-adaptivecard/python/controllers/auth_routes.py b/samples/bot-auth0-adaptivecard/python/controllers/auth_routes.py index bfe177e42c..53bf3648ff 100644 --- a/samples/bot-auth0-adaptivecard/python/controllers/auth_routes.py +++ b/samples/bot-auth0-adaptivecard/python/controllers/auth_routes.py @@ -10,10 +10,8 @@ async def auth_callback(request: Request): code = request.query_params.get("code") state = request.query_params.get("state") - if not code or not state: raise HTTPException(status_code=400, detail="Missing code or state in the callback") - try: async with httpx.AsyncClient() as client: response = await client.post( @@ -27,13 +25,10 @@ async def auth_callback(request: Request): "redirect_uri": f"{os.environ['BOT_ENDPOINT']}/api/auth/callback" } ) - if response.status_code != 200: raise HTTPException(status_code=500, detail="Failed to exchange code for token") - data = response.json() access_token = data.get("access_token") - if access_token: TokenStore().set_token(state, access_token) return RedirectResponse(url="/src/views/auth-end.html") @@ -42,4 +37,4 @@ async def auth_callback(request: Request): except Exception as e: print("Auth callback error:", e) - raise HTTPException(status_code=500, detail="Authentication failed") \ No newline at end of file + raise HTTPException(status_code=500, detail="Authentication failed") diff --git a/samples/bot-auth0-adaptivecard/python/env/.env.dev b/samples/bot-auth0-adaptivecard/python/env/.env.dev new file mode 100644 index 0000000000..8172044cec --- /dev/null +++ b/samples/bot-auth0-adaptivecard/python/env/.env.dev @@ -0,0 +1,17 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +TEAMS_APP_TENANT_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/env/.env.local b/samples/bot-auth0-adaptivecard/python/env/.env.local index 22987ef6bb..dd248d208d 100644 --- a/samples/bot-auth0-adaptivecard/python/env/.env.local +++ b/samples/bot-auth0-adaptivecard/python/env/.env.local @@ -2,20 +2,12 @@ # Built-in environment variables TEAMSFX_ENV=local +APP_NAME_SUFFIX=local -# Generated during provision, you can also add your own variables. If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly -BOT_ENDPOINT= -BOT_DOMAIN= -AAD_APP_CLIENT_ID= -AAD_APP_OBJECT_ID= -AAD_APP_TENANT_ID= -AAD_APP_OAUTH_AUTHORITY= -AAD_APP_OAUTH_AUTHORITY_HOST= +# Generated during provision, you can also add your own variables. +BOT_ID= TEAMS_APP_ID= TEAMS_APP_TENANT_ID= -MICROSOFT_APP_TYPE= -MICROSOFT_APP_TENANT_ID= -RESOURCE_SUFFIX= -AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP_NAME= -APP_NAME_SUFFIX= \ No newline at end of file +BOT_DOMAIN= +BOT_ENDPOINT= +BOT_OBJECT_ID= \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/infra/azure.bicep b/samples/bot-auth0-adaptivecard/python/infra/azure.bicep index 8734cb547b..0b515af787 100644 --- a/samples/bot-auth0-adaptivecard/python/infra/azure.bicep +++ b/samples/bot-auth0-adaptivecard/python/infra/azure.bicep @@ -3,40 +3,95 @@ @description('Used to generate names for all resources in this file') param resourceBaseName string -@description('Required when create Azure Bot service') -param botAadAppClientId string - -param botAppDomain string +param webAppSKU string +param linuxFxVersion string @maxLength(42) param botDisplayName string -param botServiceName string = resourceBaseName -param botServiceSku string = 'F0' +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param identityName string = resourceBaseName +param location string = resourceGroup().location +param pythonVersion string = linuxFxVersion -// Register your web service as a bot with the Bot Framework -resource botService 'Microsoft.BotService/botServices@2021-03-01' = { - kind: 'azurebot' - location: 'global' - name: botServiceName - properties: { - displayName: botDisplayName - endpoint: 'https://${botAppDomain}/api/messages' - msaAppId: botAadAppClientId - msaAppType: 'MultiTenant' - msaAppTenantId: '' - } +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + location: location + name: identityName +} + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app,linux' + location: location + name: serverfarmsName sku: { - name: botServiceSku + name: webAppSKU + } + properties:{ + reserved: true } } -// Connect the bot service to Microsoft Teams -resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { - parent: botService - location: 'global' - name: 'MsTeamsChannel' +// Web App that hosts your agent +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app,linux' + location: location + name: webAppName properties: { - channelName: 'MsTeamsChannel' + serverFarmId: serverfarm.id + siteConfig: { + alwaysOn: true + appCommandLine: 'python app.py' + linuxFxVersion: pythonVersion + appSettings: [ + { + name: 'WEBSITES_CONTAINER_START_TIME_LIMIT' + value: '900' + } + { + name: 'SCM_DO_BUILD_DURING_DEPLOYMENT' + value: 'true' + } + { + name: 'CLIENT_ID' + value: identity.properties.clientId + } + { + name: 'TENANT_ID' + value: identity.properties.tenantId + } + { + name: 'BOT_TYPE' + value: 'UserAssignedMsi' + } + ] + ftpsState: 'FtpsOnly' + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${identity.id}': {} + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + identityClientId: identity.properties.clientId + identityResourceId: identity.id + identityTenantId: identity.properties.tenantId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName } } + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName +output BOT_ID string = identity.properties.clientId +output BOT_TENANT_ID string = identity.properties.tenantId diff --git a/samples/bot-auth0-adaptivecard/python/infra/azure.parameters.json b/samples/bot-auth0-adaptivecard/python/infra/azure.parameters.json index 0ccfdab3f2..6df111bf08 100644 --- a/samples/bot-auth0-adaptivecard/python/infra/azure.parameters.json +++ b/samples/bot-auth0-adaptivecard/python/infra/azure.parameters.json @@ -1,18 +1,18 @@ { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "resourceBaseName": { - "value": "bot${{RESOURCE_SUFFIX}}" - }, - "botAadAppClientId": { - "value": "${{AAD_APP_CLIENT_ID}}" - }, - "botAppDomain": { - "value": "${{BOT_DOMAIN}}" - }, - "botDisplayName": { - "value": "AppHelloWorld" - } + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "webAppSKU": { + "value": "B3" + }, + "botDisplayName": { + "value": "bot-auth0-adaptivecard" + }, + "linuxFxVersion": { + "value": "PYTHON|3.12" } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/infra/botRegistration/azurebot.bicep b/samples/bot-auth0-adaptivecard/python/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..a5a27b8fe4 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/python/infra/botRegistration/azurebot.bicep @@ -0,0 +1,42 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param identityResourceId string +param identityClientId string +param identityTenantId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: identityClientId + msaAppMSIResourceId: identityResourceId + msaAppTenantId:identityTenantId + msaAppType:'UserAssignedMSI' + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/samples/bot-auth0-adaptivecard/python/infra/botRegistration/readme.md b/samples/bot-auth0-adaptivecard/python/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/bot-auth0-adaptivecard/python/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/m365agents.local.yml b/samples/bot-auth0-adaptivecard/python/m365agents.local.yml index a041f7cd61..6a8be1083f 100644 --- a/samples/bot-auth0-adaptivecard/python/m365agents.local.yml +++ b/samples/bot-auth0-adaptivecard/python/m365agents.local.yml @@ -1,79 +1,82 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.2 - -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:bot-auth0-adaptivecard-python +version: v1.11 provision: - # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty - - uses: aadApp/create - with: - name: bot-auth0-adaptivecard-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. - generateClientSecret: true # If the value is false, the action will not generate client secret for you - signInAudience: "AzureADMultipleOrgs" # Multitenant - writeToEnvironmentFile: # Write the information of created resources into environment file for the specified environment variable(s). - clientId: AAD_APP_CLIENT_ID - clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file - objectId: AAD_APP_OBJECT_ID - tenantId: AAD_APP_TENANT_ID - authority: AAD_APP_OAUTH_AUTHORITY - authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST - - # Creates a Teams app + # Creates an app - uses: teamsApp/create with: - # Teams app name + # app name name: bot-auth0-adaptivecard${{APP_NAME_SUFFIX}} # Write the information of created resources into environment file for # the specified environment variable(s). writeToEnvironmentFile: teamsAppId: TEAMS_APP_ID - - uses: arm/deploy # Deploy given ARM templates parallelly. + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: bot-auth0-adaptivecard${{APP_NAME_SUFFIX}} + generateClientSecret: true + generateServicePrincipal: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + # Update the AAD app manifest + - uses: aadApp/update with: - subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} # The AZURE_SUBSCRIPTION_ID is a built-in environment variable. TeamsFx will ask you select one subscription if its value is empty. You're free to reference other environment varialbe here, but TeamsFx will not ask you to select subscription if it's empty in this case. - resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} # The AZURE_RESOURCE_GROUP_NAME is a built-in environment variable. TeamsFx will ask you to select or create one resource group if its value is empty. You're free to reference other environment varialbe here, but TeamsFx will not ask you to select or create resource grouop if it's empty in this case. - templates: - - path: ./infra/azure.bicep - parameters: ./infra/azure.parameters.json - deploymentName: Create-resources-for-bot - bicepCliVersion: v0.9.1 # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you, will use bicep CLI in PATH if you remove this config. + # Relative path to aad app manifest + manifestPath: ./aad.manifest.json + outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json - # Validate using manifest schema - - uses: teamsApp/validateManifest + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create with: - # Path to manifest template - manifestPath: ./appManifest/manifest.json + botId: ${{BOT_ID}} + name: bot-auth0-adaptivecard + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams - # Build Teams app package with latest env value + # Build app package with latest env value - uses: teamsApp/zipAppPackage with: # Path to manifest template - manifestPath: ./appManifest/manifest.json - outputZipPath: ./appManifest/build/appManifest.${{TEAMSFX_ENV}}.zip - outputJsonPath: ./appManifest/build/manifest.${{TEAMSFX_ENV}}.json + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + # Validate app package using validation rules - uses: teamsApp/validateAppPackage with: # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appManifest/build/appManifest.${{TEAMSFX_ENV}}.zip + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - # Apply the Teams app manifest to an existing Teams app in + # Apply the app manifest to an existing app in # Developer Portal. - # Will use the app id in manifest file to determine which Teams app to update. + # Will use the app id in manifest file to determine which app to update. - uses: teamsApp/update with: # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appManifest/build/appManifest.${{TEAMSFX_ENV}}.zip + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip deploy: # Generate runtime environment variables - uses: file/createOrUpdateEnvironmentFile with: - target: ./.env - envs: - MicrosoftAppId: ${{AAD_APP_CLIENT_ID}} - MicrosoftAppPassword: ${{SECRET_AAD_APP_CLIENT_SECRET}} - BOT_ENDPOINT: ${{BOT_ENDPOINT}} \ No newline at end of file + target: ./.env + envs: + PORT: 3978 + CLIENT_ID: ${{BOT_ID}} + CLIENT_SECRET: ${{SECRET_BOT_PASSWORD}} + TENANT_ID: ${{TEAMS_APP_TENANT_ID}} + BOT_ENDPOINT: ${{BOT_ENDPOINT}} diff --git a/samples/bot-auth0-adaptivecard/python/m365agents.playground.yml b/samples/bot-auth0-adaptivecard/python/m365agents.playground.yml new file mode 100644 index 0000000000..66b018629e --- /dev/null +++ b/samples/bot-auth0-adaptivecard/python/m365agents.playground.yml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.9 + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.1 + symlinkDir: ./devTools/playground + nodejs: + symlinkDir: ./devTools/nodejs + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.env + envs: + PORT: 3978 + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} + CLIENT_ID: "" + CLIENT_SECRET: "" diff --git a/samples/bot-auth0-adaptivecard/python/m365agents.yml b/samples/bot-auth0-adaptivecard/python/m365agents.yml index d23cce4e6e..5747f0e4c5 100644 --- a/samples/bot-auth0-adaptivecard/python/m365agents.yml +++ b/samples/bot-auth0-adaptivecard/python/m365agents.yml @@ -1,8 +1,127 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.2 +version: v1.9 -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:bot-auth0-adaptivecard-python environmentFolderPath: ./env + +# Triggered when 'teamsfx provision' is executed +provision: + # Creates an app + - uses: teamsApp/create + with: + # app name + name: bot-auth0-adaptivecard${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-tab + # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the app manifest to an existing app in + # Developer Portal. + # Will use the app id in manifest file to determine which app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsfx deploy' is executed +deploy: + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: src + # Ignore file location, leave blank will ignore nothing + ignoreFile: .webappignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + +# Triggered when 'teamsapp publish' is executed +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the app manifest to an existing app in + # Developer Portal. + # Will use the app id in manifest file to determine which app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID diff --git a/samples/bot-auth0-adaptivecard/python/requirements.txt b/samples/bot-auth0-adaptivecard/python/requirements.txt deleted file mode 100644 index af41cd7b0d..0000000000 --- a/samples/bot-auth0-adaptivecard/python/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -requests==2.31.0 -botbuilder-integration-aiohttp>=4.14.5 -fastapi -uvicorn -httpx \ No newline at end of file diff --git a/samples/bot-auth0-adaptivecard/python/src/app.py b/samples/bot-auth0-adaptivecard/python/src/app.py new file mode 100644 index 0000000000..4d1b5cac59 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/python/src/app.py @@ -0,0 +1,173 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import asyncio +import os +import sys +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +import requests +from fastapi.staticfiles import StaticFiles +from microsoft_teams.apps import App, ActivityContext +from microsoft_teams.apps.http_plugin import HttpPlugin +from microsoft_teams.api import MessageActivity, MessageActivityInput, Attachment +from services.token_store import TokenStore +from controllers.auth_routes import router as auth_router +from config import Config + +config = Config() + +# Create the Teams App +app = App( + client_id=config.APP_ID, + client_secret=config.APP_PASSWORD, + tenant_id=config.APP_TENANTID +) + +# Token store instance +token_store = TokenStore() + +# Handle incoming message activities from users +@app.on_message +async def on_message_activity(ctx: ActivityContext[MessageActivity]): + user_id = ctx.activity.from_.id + text = (ctx.activity.text or "").strip().lower() + if text == "logout": + token_store.remove_token(user_id) + logout_url = ( + f"https://{config.AUTH0_DOMAIN}/v2/logout" + f"?client_id={config.AUTH0_CLIENT_ID}" + ) + logout_card = Attachment( + content={ + "type": "AdaptiveCard", + "version": "1.3", + "body": [ + { + "type": "TextBlock", + "text": "You've been logged out.", + "size": "Medium", + "weight": "Bolder", + "wrap": True, + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Logout from Auth0", + "url": logout_url, + } + ], + }, + content_type="application/vnd.microsoft.card.adaptive" + ) + message = MessageActivityInput(attachments=[logout_card]) + await ctx.send(message) + return + access_token = token_store.get_token(user_id) + if access_token: + if text == "profile details": + try: + response = requests.get( + f"https://{config.AUTH0_DOMAIN}/userinfo", + headers={"Authorization": f"Bearer {access_token}"}, + ) + if response.status_code == 200: + profile_data = response.json() + profile_card = Attachment( + content={ + "type": "AdaptiveCard", + "version": "1.3", + "body": [ + { + "type": "TextBlock", + "text": "Auth0 Profile", + "size": "Large", + "weight": "Bolder", + "wrap": True, + }, + { + "type": "Image", + "url": profile_data.get("picture", "https://via.placeholder.com/150"), + "size": "Medium", + "style": "Person", + }, + { + "type": "TextBlock", + "text": f"Name: {profile_data.get('name')}", + "wrap": True, + }, + { + "type": "TextBlock", + "text": f"Email: {profile_data.get('email')}", + "wrap": True, + }, + ], + }, + content_type="application/vnd.microsoft.card.adaptive" + ) + message = MessageActivityInput(attachments=[profile_card]) + await ctx.send(message) + else: + await ctx.send("Failed to fetch profile details.") + except Exception as e: + print(f"Error fetching profile: {e}") + await ctx.send("Error retrieving profile details.") + else: + await ctx.send("Say 'profile details' to get your profile or 'logout' to log out.") + else: + login_url = generate_login_url(user_id) + login_card = Attachment( + content={ + "type": "AdaptiveCard", + "version": "1.3", + "body": [ + { + "type": "TextBlock", + "text": "Login Required", + "size": "Medium", + "weight": "Bolder", + "wrap": True, + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Login", + "url": login_url, + } + ], + }, + content_type="application/vnd.microsoft.card.adaptive" + ) + message = MessageActivityInput(attachments=[login_card]) + await ctx.send(message) + + +# Generate Auth0 login URL with user context +def generate_login_url(user_id: str) -> str: + return ( + f"https://{config.AUTH0_DOMAIN}/authorize" + f"?response_type=code&client_id={config.AUTH0_CLIENT_ID}" + f"&redirect_uri={config.BOT_ENDPOINT}/api/auth/callback" + f"&scope=openid profile email" + f"&state={user_id}" + ) + + +# Configure custom FastAPI routes for authentication +def setup_custom_routes(): + http_plugin = next((p for p in app.plugins if isinstance(p, HttpPlugin)), None) + fastapi_app = http_plugin.app + fastapi_app.include_router(auth_router, prefix="/api/auth") + fastapi_app.mount("/src/views", StaticFiles(directory=os.path.join(os.path.dirname(__file__), "views")), name="views") + + +# Start the bot application +async def main(): + setup_custom_routes() + await app.start() + print(f"\nBot started, app listening to port {config.PORT}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/samples/bot-auth0-adaptivecard/python/src/config.py b/samples/bot-auth0-adaptivecard/python/src/config.py new file mode 100644 index 0000000000..daf633dfdf --- /dev/null +++ b/samples/bot-auth0-adaptivecard/python/src/config.py @@ -0,0 +1,16 @@ +import os + +class Config: + """Bot Configuration""" + + PORT = int(os.environ.get("PORT", 3978)) + APP_ID = os.environ.get("CLIENT_ID", "") + APP_PASSWORD = os.environ.get("CLIENT_SECRET", "") + APP_TYPE = os.environ.get("BOT_TYPE", "") + APP_TENANTID = os.environ.get("TENANT_ID", "") + + # Auth0 Configuration + AUTH0_DOMAIN = os.environ.get("AUTH0_DOMAIN", "") + AUTH0_CLIENT_ID = os.environ.get("AUTH0_CLIENT_ID", "") + AUTH0_CLIENT_SECRET = os.environ.get("AUTH0_CLIENT_SECRET", "") + BOT_ENDPOINT = os.environ.get("BOT_ENDPOINT", "") diff --git a/samples/bot-auth0-adaptivecard/python/src/requirements.txt b/samples/bot-auth0-adaptivecard/python/src/requirements.txt new file mode 100644 index 0000000000..0674b6e736 --- /dev/null +++ b/samples/bot-auth0-adaptivecard/python/src/requirements.txt @@ -0,0 +1,7 @@ +microsoft-teams-apps>=2.0.0 +microsoft-teams-api>=0.1.0 +requests>=2.31.0 +fastapi>=0.100.0 +uvicorn>=0.23.0 +httpx>=0.24.0 +python-dotenv>=1.0.0 \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/M365Agent/.gitignore b/samples/msgext-message-reminder/csharp/M365Agent/.gitignore new file mode 100644 index 0000000000..c5cae9258c --- /dev/null +++ b/samples/msgext-message-reminder/csharp/M365Agent/.gitignore @@ -0,0 +1,10 @@ +# TeamsFx files +build +appPackage/build +env/.env.*.user +env/.env.local +appsettings.Development.json +.deployment + +# User-specific files +*.user diff --git a/samples/msgext-message-reminder/csharp/M365Agent/M365Agent.atkproj b/samples/msgext-message-reminder/csharp/M365Agent/M365Agent.atkproj new file mode 100644 index 0000000000..124eb75046 --- /dev/null +++ b/samples/msgext-message-reminder/csharp/M365Agent/M365Agent.atkproj @@ -0,0 +1,9 @@ + + + + b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf + + + + + \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/M365Agent/M365Agent.ttkproj b/samples/msgext-message-reminder/csharp/M365Agent/M365Agent.ttkproj deleted file mode 100644 index a7b0ff7d79..0000000000 --- a/samples/msgext-message-reminder/csharp/M365Agent/M365Agent.ttkproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - f5f9dc2a-6f6a-4495-b5ec-e149c3716799 - - - - - - - - - - \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/M365Agent/appPackage/color.png b/samples/msgext-message-reminder/csharp/M365Agent/appPackage/color.png index b8cf81afbe..01aa37e347 100644 Binary files a/samples/msgext-message-reminder/csharp/M365Agent/appPackage/color.png and b/samples/msgext-message-reminder/csharp/M365Agent/appPackage/color.png differ diff --git a/samples/msgext-message-reminder/csharp/M365Agent/appPackage/manifest.json b/samples/msgext-message-reminder/csharp/M365Agent/appPackage/manifest.json index c3b65bde14..b28f5d5eaa 100644 --- a/samples/msgext-message-reminder/csharp/M365Agent/appPackage/manifest.json +++ b/samples/msgext-message-reminder/csharp/M365Agent/appPackage/manifest.json @@ -1,6 +1,6 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json", - "manifestVersion": "1.19", +{ + "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.23/MicrosoftTeams.schema.json", + "manifestVersion": "1.23", "version": "1.0.0", "id": "${{TEAMS_APP_ID}}", "developer": { @@ -24,7 +24,7 @@ "accentColor": "#60A18E", "bots": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "${{BOT_ID}}", "scopes": [ "personal", "team" @@ -34,7 +34,7 @@ ], "composeExtensions": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "${{BOT_ID}}", "canUpdateConfiguration": true, "commands": [ { diff --git a/samples/msgext-message-reminder/csharp/M365Agent/appPackage/outline.png b/samples/msgext-message-reminder/csharp/M365Agent/appPackage/outline.png index 2c3bf6fa65..f7a4c86447 100644 Binary files a/samples/msgext-message-reminder/csharp/M365Agent/appPackage/outline.png and b/samples/msgext-message-reminder/csharp/M365Agent/appPackage/outline.png differ diff --git a/samples/msgext-message-reminder/csharp/M365Agent/env/.env.dev b/samples/msgext-message-reminder/csharp/M365Agent/env/.env.dev new file mode 100644 index 0000000000..df4f9da508 --- /dev/null +++ b/samples/msgext-message-reminder/csharp/M365Agent/env/.env.dev @@ -0,0 +1,15 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/M365Agent/env/.env.local b/samples/msgext-message-reminder/csharp/M365Agent/env/.env.local index ae4c5f84cf..54159d3559 100644 --- a/samples/msgext-message-reminder/csharp/M365Agent/env/.env.local +++ b/samples/msgext-message-reminder/csharp/M365Agent/env/.env.local @@ -5,21 +5,11 @@ TEAMSFX_ENV=local APP_NAME_SUFFIX=local # Generated during provision, you can also add your own variables. -BOT_ID= -TEAMS_APP_ID= -TEAMSFX_M365_USER_NAME= - -BOT_ENDPOINT= -BOT_DOMAIN= - -RESOURCE_SUFFIX= -AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP_NAME= -AAD_APP_CLIENT_ID= -AAD_APP_OBJECT_ID= -AAD_APP_TENANT_ID= -AAD_APP_OAUTH_AUTHORITY= -AAD_APP_OAUTH_AUTHORITY_HOST= -TEAMS_APP_TENANT_ID= -MICROSOFT_APP_TYPE= -MICROSOFT_APP_TENANT_ID= \ No newline at end of file +BOT_ID=9de984fd-33c2-4436-aec8-ad88c0915384 +TEAMS_APP_ID=0201f807-a0a1-4518-8dbb-7cf8ea8ec618 +TEAMS_APP_TENANT_ID=bd841a6d-0866-4069-a8be-a057f49da840 +BOT_OBJECT_ID=e32628fd-7aa9-413a-8c96-d485d5350bfe +TEAMSFX_M365_USER_NAME=admin@M365x71149791.onmicrosoft.com + +BOT_ENDPOINT=https://qkvgcfw5-5130.inc1.devtunnels.ms +BOT_DOMAIN=qkvgcfw5-5130.inc1.devtunnels.ms \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/M365Agent/infra/azure.bicep b/samples/msgext-message-reminder/csharp/M365Agent/infra/azure.bicep index c3ce051b3d..658e412a21 100644 --- a/samples/msgext-message-reminder/csharp/M365Agent/infra/azure.bicep +++ b/samples/msgext-message-reminder/csharp/M365Agent/infra/azure.bicep @@ -3,42 +3,84 @@ @description('Used to generate names for all resources in this file') param resourceBaseName string -@description('Required when create Azure Bot service') -param botAadAppClientId string - -param botAppDomain string +param webAppSKU string @maxLength(42) param botDisplayName string -param botServiceName string = resourceBaseName -param botServiceSku string = 'F0' -param microsoftAppType string -param microsoftAppTenantId string +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param identityName string = resourceBaseName +param location string = resourceGroup().location -// Register your web service as a bot with the Bot Framework -resource botService 'Microsoft.BotService/botServices@2021-03-01' = { - kind: 'azurebot' - location: 'global' - name: botServiceName - properties: { - displayName: botDisplayName - endpoint: 'https://${botAppDomain}/api/messages' - msaAppId: botAadAppClientId - msaAppType: microsoftAppType - msaAppTenantId: microsoftAppType == 'SingleTenant' ? microsoftAppTenantId : '' - } +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + location: location + name: identityName +} + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName sku: { - name: botServiceSku + name: webAppSKU } } -// Connect the bot service to Microsoft Teams -resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { - parent: botService - location: 'global' - name: 'MsTeamsChannel' +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName properties: { - channelName: 'MsTeamsChannel' + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' + } + { + name: 'Teams__ClientId' + value: identity.properties.clientId + } + { + name: 'Teams__TenantId' + value: identity.properties.tenantId + } + { + name: 'Teams__BotType' + value: 'UserAssignedMsi' + } + ] + ftpsState: 'FtpsOnly' + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${identity.id}': {} + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + identityClientId: identity.properties.clientId + identityResourceId: identity.id + identityTenantId: identity.properties.tenantId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName } } + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName +output BOT_ID string = identity.properties.clientId +output BOT_TENANT_ID string = identity.properties.tenantId diff --git a/samples/msgext-message-reminder/csharp/M365Agent/infra/azure.parameters.json b/samples/msgext-message-reminder/csharp/M365Agent/infra/azure.parameters.json index 3eadb2ff32..e7d1ae8fc4 100644 --- a/samples/msgext-message-reminder/csharp/M365Agent/infra/azure.parameters.json +++ b/samples/msgext-message-reminder/csharp/M365Agent/infra/azure.parameters.json @@ -1,24 +1,15 @@ { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "resourceBaseName": { - "value": "bot${{RESOURCE_SUFFIX}}" - }, - "botAadAppClientId": { - "value": "${{AAD_APP_CLIENT_ID}}" - }, - "botAppDomain": { - "value": "${{BOT_DOMAIN}}" - }, - "botDisplayName": { - "value": "msgext-message-reminder" - }, - "microsoftAppType": { - "value": "${{MICROSOFT_APP_TYPE}}" - }, - "microsoftAppTenantId": { - "value": "${{MICROSOFT_APP_TENANT_ID}}" + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "MessagingExtensionReminder" + } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/M365Agent/infra/botRegistration/azurebot.bicep b/samples/msgext-message-reminder/csharp/M365Agent/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..a5a27b8fe4 --- /dev/null +++ b/samples/msgext-message-reminder/csharp/M365Agent/infra/botRegistration/azurebot.bicep @@ -0,0 +1,42 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param identityResourceId string +param identityClientId string +param identityTenantId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: identityClientId + msaAppMSIResourceId: identityResourceId + msaAppTenantId:identityTenantId + msaAppType:'UserAssignedMSI' + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/samples/msgext-message-reminder/csharp/M365Agent/infra/botRegistration/readme.md b/samples/msgext-message-reminder/csharp/M365Agent/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/msgext-message-reminder/csharp/M365Agent/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/M365Agent/launchSettings.json b/samples/msgext-message-reminder/csharp/M365Agent/launchSettings.json index 8c76c70d9e..2af8ce7a8a 100644 --- a/samples/msgext-message-reminder/csharp/M365Agent/launchSettings.json +++ b/samples/msgext-message-reminder/csharp/M365Agent/launchSettings.json @@ -1,17 +1,25 @@ { - "profiles": { - // Debug project within Teams - "Microsoft Teams (browser)": { - "commandName": "Project", - "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" - } - }, - // Launch project within Teams without prepare app dependencies - "Microsoft Teams (browser) (skip update app)": { - "commandName": "Project", - "environmentVariables": { - "UPDATE_TEAMS_APP": "false" - }, - "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" - } + "profiles": { + // Launch project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground (browser)": { + "commandName": "Project", + "environmentVariables": { + "UPDATE_TEAMS_APP": "false", + "M365_AGENTS_PLAYGROUND_TARGET_SDK": "teams-ai-v2-dotnet" + }, + "launchTestTool": true, + "launchUrl": "http://localhost:56150", + }, + // Launch project within Teams + "Microsoft Teams (browser)": { + "commandName": "Project", + "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}", + }, + // Launch project within Teams without prepare app dependencies + "Microsoft Teams (browser) (skip update app)": { + "commandName": "Project", + "environmentVariables": { "UPDATE_TEAMS_APP": "false" }, + "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" + }, + } } \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/M365Agent/m365agents.local.yml b/samples/msgext-message-reminder/csharp/M365Agent/m365agents.local.yml index 34dbfe219d..d4222f31c4 100644 --- a/samples/msgext-message-reminder/csharp/M365Agent/m365agents.local.yml +++ b/samples/msgext-message-reminder/csharp/M365Agent/m365agents.local.yml @@ -1,83 +1,69 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.2 - -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:msgext-message-reminder-csharp +version: v1.11 provision: - - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty - with: - name: msgext-message-reminder-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. - generateClientSecret: true # If the value is false, the action will not generate client secret for you - signInAudience: "AzureADMultipleOrgs" # Multitenant - writeToEnvironmentFile: # Write the information of created resources into environment file for the specified environment variable(s). - clientId: AAD_APP_CLIENT_ID - clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file - objectId: AAD_APP_OBJECT_ID - tenantId: AAD_APP_TENANT_ID - authority: AAD_APP_OAUTH_AUTHORITY - authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST - # Creates a Teams app - uses: teamsApp/create with: # Teams app name - name: msgext-message-reminder-${{TEAMSFX_ENV}} + name: MessagingExtensionReminder${{APP_NAME_SUFFIX}} # Write the information of created resources into environment file for # the specified environment variable(s). - writeToEnvironmentFile: + writeToEnvironmentFile: teamsAppId: TEAMS_APP_ID - - uses: script + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create with: - run: - echo "::set-teamsfx-env MICROSOFT_APP_TYPE=SingleTenant"; - echo "::set-teamsfx-env MICROSOFT_APP_TENANT_ID=${{AAD_APP_TENANT_ID}}"; + # The Microsoft Entra application's display name + name: MessagingExtensionReminder${{APP_NAME_SUFFIX}} + generateClientSecret: true + generateServicePrincipal: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + # Generate runtime appsettings to JSON file - uses: file/createOrUpdateJsonFile with: - target: ../MessagingExtensionReminder/appsettings.json + target: ../MessagingExtensionReminder/appsettings.Development.json content: - MicrosoftAppType: SingleTenant - MicrosoftAppId: ${{AAD_APP_CLIENT_ID}} - MicrosoftAppPassword: ${{SECRET_AAD_APP_CLIENT_SECRET}} - MicrosoftAppTenantId: ${{AAD_APP_TENANT_ID}} + Teams: + ClientId: ${{BOT_ID}} + ClientSecret: ${{SECRET_BOT_PASSWORD}} + TenantId: ${{TEAMS_APP_TENANT_ID}} ApplicationBaseUrl: ${{BOT_ENDPOINT}} - - uses: arm/deploy # Deploy given ARM templates parallelly. + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create with: - subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} # The AZURE_SUBSCRIPTION_ID is a built-in environment variable. TeamsFx will ask you select one subscription if its value is empty. You're free to reference other environment varialbe here, but TeamsFx will not ask you to select subscription if it's empty in this case. - resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} # The AZURE_RESOURCE_GROUP_NAME is a built-in environment variable. TeamsFx will ask you to select or create one resource group if its value is empty. You're free to reference other environment varialbe here, but TeamsFx will not ask you to select or create resource grouop if it's empty in this case. - templates: - - path: ./infra/azure.bicep - parameters: ./infra/azure.parameters.json - deploymentName: Create-resources-for-bot - bicepCliVersion: v0.9.1 # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you, will use bicep CLI in PATH if you remove this config. - -# manifest file to determine which AAD app to update. - - uses: aadApp/update - with: - # Relative path to this file. Environment variables in manifest will - # be replaced before apply to AAD app - manifestPath: ./aad.manifest.json - outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json + botId: ${{BOT_ID}} + name: MessagingExtensionReminder + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams # Validate using manifest schema - uses: teamsApp/validateManifest with: # Path to manifest template manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: # Path to manifest template manifestPath: ./appPackage/manifest.json outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + outputFolder: ./appPackage/build # Validate app package using validation rules - uses: teamsApp/validateAppPackage with: @@ -90,4 +76,4 @@ provision: - uses: teamsApp/update with: # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip \ No newline at end of file + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip diff --git a/samples/msgext-message-reminder/csharp/M365Agent/m365agents.yml b/samples/msgext-message-reminder/csharp/M365Agent/m365agents.yml index 335d3fbd9c..eb50a9a2e9 100644 --- a/samples/msgext-message-reminder/csharp/M365Agent/m365agents.yml +++ b/samples/msgext-message-reminder/csharp/M365Agent/m365agents.yml @@ -1,9 +1,89 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.2 +version: v1.9 -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:msgext-message-reminder-csharp +environmentFolderPath: ./env -environmentFolderPath: ./env \ No newline at end of file +# Triggered when 'teamsapp provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: MessagingExtensionReminder${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + - uses: cli/runDotnetCommand + with: + args: publish --configuration Release --runtime win-x86 --self-contained + MessagingExtensionReminder.csproj + workingDirectory: ../MessagingExtensionReminder + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: bin/Release/net10.0/win-x86/publish + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + workingDirectory: ../MessagingExtensionReminder +projectId: b9bb6a7a-f1f5-4c58-be15-f969db6ea931 diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.sln b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.sln deleted file mode 100644 index 48f640e8b4..0000000000 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.10.35027.167 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingExtensionReminder", "MessagingExtensionReminder\MessagingExtensionReminder.csproj", "{BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}" -EndProject -Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{F5F9DC2A-6F6A-4495-B5EC-E149C3716799}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0F04FC80-4151-4063-A369-2D90131B116E}" - ProjectSection(SolutionItems) = preProject - MessagingExtensionReminder.slnLaunch.user = MessagingExtensionReminder.slnLaunch.user - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}.Release|Any CPU.Build.0 = Release|Any CPU - {F5F9DC2A-6F6A-4495-B5EC-E149C3716799}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F5F9DC2A-6F6A-4495-B5EC-E149C3716799}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5F9DC2A-6F6A-4495-B5EC-E149C3716799}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {F5F9DC2A-6F6A-4495-B5EC-E149C3716799}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F5F9DC2A-6F6A-4495-B5EC-E149C3716799}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {8F7F570D-74C3-4CF3-AACD-361A3D3F8E37} - EndGlobalSection -EndGlobal diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.slnLaunch.user b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.slnLaunch.user index f5bc2867d1..beb5d0c30c 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.slnLaunch.user +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.slnLaunch.user @@ -1,31 +1,52 @@ [ { - "Name": "Microsoft Teams (browser)", + "Name": "Microsoft 365 Agents Playground (browser)", "Projects": [ { - "Path": "MessagingExtensionReminder\\MessagingExtensionReminder.csproj", - "Action": "Start", - "DebugTarget": "Start Project" + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft 365 Agents Playground (browser)" }, { - "Path": "M365Agent\\M365Agent.ttkproj", - "Action": "StartWithoutDebugging", - "DebugTarget": "Microsoft Teams (Browser)" + "Path": "MessagingExtensionReminder\\MessagingExtensionReminder.csproj", + "Name": "MessagingExtensionReminder\\MessagingExtensionReminder.csproj", + "Action": "Start", + "DebugTarget": "Microsoft 365 Agents Playground" } ] }, { - "Name": "Microsoft Teams (browser) (skip update app)", + "Name": "Microsoft Teams (browser)", "Projects": [ + { + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft Teams (browser)" + }, { "Path": "MessagingExtensionReminder\\MessagingExtensionReminder.csproj", + "Name": "MessagingExtensionReminder\\MessagingExtensionReminder.csproj", "Action": "Start", "DebugTarget": "Start Project" - }, + } + ] + }, + { + "Name": "Microsoft Teams (browser) (skip update app)", + "Projects": [ { - "Path": "M365Agent\\M365Agent.ttkproj", + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", "Action": "StartWithoutDebugging", "DebugTarget": "Microsoft Teams (browser) (skip update app)" + }, + { + "Path": "MessagingExtensionReminder\\MessagingExtensionReminder.csproj", + "Name": "MessagingExtensionReminder\\MessagingExtensionReminder.csproj", + "Action": "Start", + "DebugTarget": "Start Project" } ] } diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.slnx b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.slnx new file mode 100644 index 0000000000..8543d22bb6 --- /dev/null +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/.gitignore b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/.gitignore index 7466c01f9c..77c7154916 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/.gitignore +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/.gitignore @@ -5,6 +5,7 @@ env/.env.*.user env/.env.local appsettings.Development.json .deployment +appsettings.Playground.json # User-specific files *.user @@ -22,4 +23,8 @@ bld/ [Ll]og/ # Notification local store -.notification.localstore.json \ No newline at end of file +.notification.localstore.json +.notification.playgroundstore.json + +# devTools +devTools/ \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/AdapterWithErrorHandler.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/AdapterWithErrorHandler.cs deleted file mode 100644 index ce7d9ce297..0000000000 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/AdapterWithErrorHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Connector; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Schema; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using System.Net.Http; - -namespace MessagingExtensionReminder -{ - public class AdapterWithErrorHandler : CloudAdapter - { - public AdapterWithErrorHandler(IConfiguration configuration, IHttpClientFactory httpClientFactory, ILogger logger, ConversationState conversationState = default) - : base(configuration, httpClientFactory, logger) - { - OnTurnError = async (turnContext, exception) => - { - // Log any leaked exception from the application. - // NOTE: In production environment, you should consider logging this to - // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how - // to add telemetry capture to your bot. - logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); - - // Uncomment below commented line for local debugging.. - // await turnContext.SendActivityAsync($"Sorry, it looks like something went wrong. Exception Caught: {exception.Message}"); - - // Send a trace activity, which will be displayed in the Bot Framework Emulator - await SendTraceActivityAsync(turnContext, exception); - }; - } - - private static async Task SendTraceActivityAsync(ITurnContext turnContext, Exception exception) - { - // Only send a trace activity if we're talking to the Bot Framework Emulator - if (turnContext.Activity.ChannelId == Channels.Emulator) - { - Activity traceActivity = new Activity(ActivityTypes.Trace) - { - Label = "TurnError", - Name = "OnTurnError Trace", - Value = exception.Message, - ValueType = "https://www.botframework.com/schemas/error", - }; - - // Send a trace activity - await turnContext.SendActivityAsync(traceActivity); - } - } - } -} diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Bots/ActivityBot.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Bots/ActivityBot.cs deleted file mode 100644 index a3cc48b5fa..0000000000 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Bots/ActivityBot.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Generated with Bot Builder V4 SDK Template for Visual Studio v4.14.0 - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MessagingExtensionReminder.Models; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Teams; -using Microsoft.Bot.Schema; -using Microsoft.Bot.Schema.Teams; -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json.Linq; - -namespace MessagingExtensionReminder.Bots -{ - /// - /// Bot Activity handler class. - /// - public class ActivityBot : TeamsActivityHandler - { - private readonly string _applicationBaseUrl; - protected readonly BotState _conversationState; - private readonly ConcurrentDictionary _conversationReferences; - private readonly ConcurrentDictionary> _taskDetails; - - public ActivityBot(IConfiguration configuration, ConversationState conversationState, ConcurrentDictionary conversationReferences, ConcurrentDictionary> taskDetails) - { - _conversationReferences = conversationReferences; - _conversationState = conversationState; - _taskDetails = taskDetails; - _applicationBaseUrl = configuration["ApplicationBaseUrl"] ?? throw new NullReferenceException("ApplicationBaseUrl"); - } - - /// - /// When OnTurn method receives a submit invoke activity on bot turn, it calls this method. - /// - /// Context object containing information cached for a single turn of conversation with a user. - /// Provides context for a turn of a bot and. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// A task that represents a task module response. - protected override async Task OnTeamsMessagingExtensionFetchTaskAsync( - ITurnContext turnContext, - MessagingExtensionAction action, - CancellationToken cancellationToken) - { - var title = string.Empty; - var description = string.Empty; - - if(action.MessagePayload.Subject != null && turnContext.Activity.Conversation.ConversationType!="personal" && action.MessagePayload.Subject != "") - { - description = action.MessagePayload.Body.Content; - title = action.MessagePayload.Subject; - } - else - { - title = action.MessagePayload.Body.Content; - } - - return this.GetTaskModuleResponse(title, description); - } - - /// - /// Handle request from bot. - /// - /// The turn context. - /// The cancellation token. - /// A task that represents the work queued to execute. - public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)) - { - await base.OnTurnAsync(turnContext, cancellationToken); - - // Save any state changes that might have occurred during the turn. - await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); - } - - /// - /// Invoked when bot (like a user) are added to the conversation. - /// - /// A list of all the members added to the conversation. - /// Context object containing information cached for a single turn of conversation with a user. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - /// - protected override async Task OnConversationUpdateActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - if (turnContext.Activity.MembersAdded != null && turnContext.Activity.MembersAdded.Any(member => member.Id == turnContext.Activity.Recipient.Id)) - { - await turnContext.SendActivityAsync(MessageFactory.Text($"Hello and welcome! With this sample you can schedule a message reminder by selecting `...` over the message then select more action and then create-reminder and you wil get reminder of the message at scheduled date and time."), cancellationToken); - } - } - - /// - /// Handle message extension submit action task received by the bot. - /// - /// Context object containing information cached for a single turn of conversation with a user. - /// Messaging extension action value payload. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// Response of messaging extension action. - /// - /// Reference link: https://docs.microsoft.com/en-us/dotnet/api/microsoft.bot.builder.teams.teamsactivityhandler.onteamsmessagingextensionfetchtaskasync?view=botbuilder-dotnet-stable. - /// - protected override async Task OnTeamsMessagingExtensionSubmitActionAsync( - ITurnContext turnContext, - MessagingExtensionAction action, - CancellationToken cancellationToken) - { - AddConversationReference(turnContext.Activity as Activity); - var asJobject = JObject.FromObject(action.Data); - var title = (string)asJobject.ToObject>()?.Title; - var description = (string)asJobject.ToObject>()?.Description; - var dateTime = (DateTime)asJobject.ToObject>()?.DateTime; - - var date = dateTime.ToLocalTime(); - - var currentTaskList = new List(); - List taskList = new List(); - _taskDetails.TryGetValue("taskDetails", out currentTaskList); - - var taskDetails = new SaveTaskDetail() - { - Description = description, - Title = title, - DateTime = new DateTimeOffset(date.Year, date.Month, date.Day, date.Hour, date.Minute, 0, TimeSpan.Zero), - }; - - if (currentTaskList == null) - { - taskList.Add(taskDetails); - _taskDetails.AddOrUpdate("taskDetails", taskList, (key, newValue) => taskList); - } - else - { - currentTaskList.Add(taskDetails); - _taskDetails.AddOrUpdate("taskDetails", currentTaskList, (key, newValue) => currentTaskList); - } - - TaskScheduler taskSchedule = new TaskScheduler(); - - taskSchedule.Start(date.Year, date.Month, date.Day, date.Hour, date.Minute, _applicationBaseUrl); - await turnContext.SendActivityAsync("Task submitted successfully. You will get reminder for the task at scheduled time"); - - return null; - } - - /// - /// Method to add conversation reference. - /// - /// Bot activity - private void AddConversationReference(Activity activity) - { - var conversationReference = activity.GetConversationReference(); - _conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference); - } - - /// - /// Get messaging extension action response object to show collection of question answers. - /// - /// Question answer card as input. - /// MessagingExtensionActionResponse object. - private MessagingExtensionActionResponse GetTaskModuleResponse(string title,string description) - { - return new MessagingExtensionActionResponse - { - Task = new TaskModuleContinueResponse() - { - Value = new TaskModuleTaskInfo - { - Url = _applicationBaseUrl + "/" + "ScheduleTask?title=" + title + "&description="+ description, - Height = 350, - Width = 400, - Title = "Schedule-task", - }, - }, - }; - } - } -} \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Config.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Config.cs new file mode 100644 index 0000000000..e642258eef --- /dev/null +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Config.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace MessagingExtensionReminder +{ + public class ConfigOptions + { + public TeamsConfigOptions Teams { get; set; } + public string ApplicationBaseUrl { get; set; } + } + + public class TeamsConfigOptions + { + public string BotType { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public string TenantId { get; set; } + } +} \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/BotController.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/BotController.cs deleted file mode 100644 index 5dbbc4a993..0000000000 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/BotController.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// -// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.14.0 - -using Microsoft.AspNetCore.Mvc; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using System.Threading.Tasks; - -namespace MessagingExtensionReminder.Controllers -{ - // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot - // implementation at runtime. Multiple different IBot implementations running at different endpoints can be - // achieved by specifying a more specific type for the bot constructor argument. - [Route("api/messages")] - [ApiController] - public class BotController : ControllerBase - { - private readonly CloudAdapter Adapter; - private readonly IBot Bot; - - public BotController(CloudAdapter adapter, IBot bot) - { - Adapter = adapter; - Bot = bot; - } - - [HttpPost, HttpGet] - public async Task PostAsync() - { - // Delegate the processing of the HTTP POST to the adapter. - // The adapter will invoke the bot. - await Adapter.ProcessAsync(Request, Response, Bot); - } - } -} diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/Controller.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/Controller.cs new file mode 100644 index 0000000000..fcf5ba305c --- /dev/null +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/Controller.cs @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Concurrent; +using System.Text.Json; +using MessagingExtensionReminder.Helpers; +using MessagingExtensionReminder.Models; +using Microsoft.Teams.Api.Cards; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Activities.Invokes; +using Microsoft.Teams.Apps.Annotations; +using Microsoft.Teams.Cards; + +namespace MessagingExtensionReminder.Controllers +{ + [TeamsController] + public class Controller + { + private readonly ConcurrentDictionary> _taskDetails; + private readonly ConcurrentDictionary _conversationReferences; + private readonly string _applicationBaseUrl; + + public Controller( + ConcurrentDictionary> taskDetails, + ConcurrentDictionary conversationReferences, + IConfiguration configuration) + { + _taskDetails = taskDetails; + _conversationReferences = conversationReferences; + _applicationBaseUrl = configuration["ApplicationBaseUrl"] ?? throw new ArgumentNullException("ApplicationBaseUrl"); + } + + // Handles message extension fetch task and displays the schedule task form + [MessageExtension.FetchTask] + public object OnMessageExtensionFetchTask( + [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.FetchTaskActivity activity, + [Context] IContext.Client client, + [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + StoreConversationReference(activity, log); + var title = string.Empty; + var description = string.Empty; + if (activity.Value?.MessagePayload?.Subject != null && + !string.IsNullOrEmpty(activity.Value.MessagePayload.Subject)) + { + description = activity.Value.MessagePayload?.Body?.Content ?? ""; + title = activity.Value.MessagePayload.Subject; + } + else + { + title = activity.Value?.MessagePayload?.Body?.Content ?? ""; + } + var taskModuleUrl = $"{_applicationBaseUrl}/ScheduleTask?title={Uri.EscapeDataString(title)}&description={Uri.EscapeDataString(description)}"; + return new + { + task = new + { + type = "continue", + value = new + { + url = taskModuleUrl, + height = 350, + width = 400, + title = "Schedule-task" + } + } + }; + } + + // Handles task submission, saves task details, and sends confirmation message + [MessageExtension.SubmitAction] + public object OnMessageExtensionSubmitAction( + [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, + [Context] IContext.Client client, + [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + StoreConversationReference(activity, log); + var data = activity.Value?.Data; + var jsonString = JsonSerializer.Serialize(data); + var taskDetails = JsonSerializer.Deserialize(jsonString); + if (taskDetails == null || taskDetails.Title == null || taskDetails.DateTime == null) + { + return MessageExtensionResponseHelper.CreateErrorActionResponse("Invalid task data"); + } + var taskDateTime = new DateTimeOffset( + taskDetails.DateTime.Value.Year, + taskDetails.DateTime.Value.Month, + taskDetails.DateTime.Value.Day, + taskDetails.DateTime.Value.Hour, + taskDetails.DateTime.Value.Minute, + 0, + DateTimeOffset.Now.Offset); + var currentTaskList = new List(); + _taskDetails.TryGetValue("taskDetails", out currentTaskList); + var saveTaskDetail = new SaveTaskDetail + { + Description = taskDetails.Description, + Title = taskDetails.Title, + DateTime = taskDateTime + }; + if (currentTaskList == null) + { + currentTaskList = new List { saveTaskDetail }; + _taskDetails.AddOrUpdate("taskDetails", currentTaskList, (key, newValue) => currentTaskList); + } + else + { + currentTaskList.Add(saveTaskDetail); + _taskDetails.AddOrUpdate("taskDetails", currentTaskList, (key, newValue) => currentTaskList); + } + var taskScheduler = new Helpers.TaskScheduler(); + taskScheduler.Start( + taskDateTime.Year, + taskDateTime.Month, + taskDateTime.Day, + taskDateTime.Hour, + taskDateTime.Minute, + _applicationBaseUrl); + var confirmationMessage = "Task submitted successfully. You will get a reminder for the task at the scheduled time."; + client.Send(confirmationMessage).Wait(); + return new { }; + } + + // Stores conversation reference for sending proactive messages + private void StoreConversationReference( + Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.FetchTaskActivity activity, + Microsoft.Teams.Common.Logging.ILogger log) + { + var conversationReference = new Microsoft.Teams.Api.ConversationReference + { + ServiceUrl = activity.ServiceUrl, + ChannelId = activity.ChannelId, + Conversation = activity.Conversation, + Bot = activity.Recipient, + User = activity.From + }; + if (!string.IsNullOrEmpty(activity.From?.Id)) + { + _conversationReferences.AddOrUpdate( + activity.From.Id, + conversationReference, + (key, oldValue) => conversationReference); + } + } + + // Stores conversation reference for sending proactive messages + private void StoreConversationReference( + Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, + Microsoft.Teams.Common.Logging.ILogger log) + { + var conversationReference = new Microsoft.Teams.Api.ConversationReference + { + ServiceUrl = activity.ServiceUrl, + ChannelId = activity.ChannelId, + Conversation = activity.Conversation, + Bot = activity.Recipient, + User = activity.From + }; + if (!string.IsNullOrEmpty(activity.From?.Id)) + { + _conversationReferences.AddOrUpdate( + activity.From.Id, + conversationReference, + (key, oldValue) => conversationReference); + } + } + } +} diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/TaskReminderController.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/TaskReminderController.cs index e3f2246c1b..d3806b21e8 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/TaskReminderController.cs +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Controllers/TaskReminderController.cs @@ -1,116 +1,52 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using AdaptiveCards; +using System.Collections.Concurrent; using MessagingExtensionReminder.Models; using Microsoft.AspNetCore.Mvc; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Schema; -using Microsoft.Extensions.Configuration; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace MessagingExtensionReminder.Controllers { - /// - /// Class with properties related to task reminder. - /// + /// Controller to handle task reminder API calls. [Route("api/task")] [ApiController] public class TaskReminderController : ControllerBase { - private readonly CloudAdapter _adapter; - private readonly string _appId; private readonly ConcurrentDictionary> _taskDetails; - private readonly ConcurrentDictionary _conversationReferences; - - public TaskReminderController(CloudAdapter adapter, - IConfiguration configuration, - ConcurrentDictionary conversationReferences, - ConcurrentDictionary> taskDetails) + private readonly ConcurrentDictionary _conversationReferences; + public TaskReminderController( + ConcurrentDictionary> taskDetails, + ConcurrentDictionary conversationReferences) { - _adapter = adapter; - _conversationReferences = conversationReferences; _taskDetails = taskDetails; - _appId = configuration["MicrosoftAppId"] ?? string.Empty; + _conversationReferences = conversationReferences; } - /// - /// This enpoint is called to send task reminder card. - /// + /// This endpoint is called by the Quartz scheduler to trigger task reminder checks. [HttpGet] - public async void GetTaskReminder() - { - foreach (var conversationReference in _conversationReferences.Values) - { - await((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, BotCallback, default(CancellationToken)); - } - } - - /// - /// Callback method to send activity. - /// - /// - /// - /// - private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken) + public Task GetTaskReminder() { - var taskList = new List(); - _taskDetails.TryGetValue("taskDetails", out taskList); - - foreach (var task in taskList) + try { - var time = new DateTimeOffset(DateTime.Now); - - if(task.DateTime.Minute == time.Minute && task.DateTime.Hour == time.Hour && task.DateTime.Day == time.Day && task.DateTime.Month == time.Month && task.DateTime.Year == time.Year) + var hasTasks = _taskDetails.TryGetValue("taskDetails", out var tasks); + var conversationCount = _conversationReferences.Count; + return Task.FromResult(Ok(new { - await turnContext.SendActivityAsync(MessageFactory.Attachment(GetAdaptiveCardForTaskReminder(task.Title, task.Description)), cancellationToken); - } + status = "success", + message = "Task reminder check completed", + timestamp = DateTime.UtcNow, + taskCount = hasTasks && tasks != null ? tasks.Count : 0, + conversationCount = conversationCount + })); } - } - - /// - /// Sample Adaptive card task reminder. - /// - private Attachment GetAdaptiveCardForTaskReminder(string title, string description) - { - AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion("1.2")) + catch (Exception ex) { - Body = new List + return Task.FromResult(StatusCode(500, new { - new AdaptiveTextBlock - { - Text = "Reminder for scheduled task!", - Weight = AdaptiveTextWeight.Bolder, - Spacing = AdaptiveSpacing.Medium, - Wrap = true - }, - new AdaptiveTextBlock - { - Text = "Task title: "+ title, - Weight = AdaptiveTextWeight.Default, - Spacing = AdaptiveSpacing.Medium, - Wrap = true - }, - new AdaptiveTextBlock - { - Text = "Task description: "+ description, - Weight = AdaptiveTextWeight.Default, - Spacing = AdaptiveSpacing.Medium, - Wrap = true - } - }, - }; - - return new Attachment() - { - ContentType = AdaptiveCard.ContentType, - Content = card, - }; + error = "Failed to process task reminders", + details = ex.Message + })); + } } } -} \ No newline at end of file +} diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helper/ScheduleTaskReminder.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helpers/ScheduleTaskReminder.cs similarity index 93% rename from samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helper/ScheduleTaskReminder.cs rename to samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helpers/ScheduleTaskReminder.cs index 6887ca0a36..721eafea8d 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helper/ScheduleTaskReminder.cs +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helpers/ScheduleTaskReminder.cs @@ -6,7 +6,7 @@ using System.Net.Http; using System.Threading.Tasks; -namespace MessagingExtensionReminder +namespace MessagingExtensionReminder.Helpers { public class ScheduleTaskReminder : IJob { diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helper/TaskScheduler.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helpers/TaskScheduler.cs similarity index 96% rename from samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helper/TaskScheduler.cs rename to samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helpers/TaskScheduler.cs index 0bdb425d66..c1547a8f0f 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helper/TaskScheduler.cs +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Helpers/TaskScheduler.cs @@ -6,7 +6,7 @@ using Quartz; using Quartz.Impl; -namespace MessagingExtensionReminder +namespace MessagingExtensionReminder.Helpers { public class TaskScheduler { diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/MessageExtensionResponseHelper.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/MessageExtensionResponseHelper.cs new file mode 100644 index 0000000000..23e9ca160b --- /dev/null +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/MessageExtensionResponseHelper.cs @@ -0,0 +1,278 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; + +using Microsoft.Teams.Api.Cards; +using Microsoft.Teams.Cards; + +namespace MessagingExtensionReminder +{ + public static class MessageExtensionResponseHelper + { + // Helper methods for creating responses + public static Microsoft.Teams.Api.MessageExtensions.Response CreateSearchResults(string query, Microsoft.Teams.Common.Logging.ILogger log) + { + var attachments = new List(); + + // Create simple search results + for (int i = 1; i <= 5; i++) + { + var card = new AdaptiveCard(new List + { + new TextBlock($"Search Result {i}") + { + Weight = TextWeight.Bolder, + Size = TextSize.Large + }, + new TextBlock($"Query: '{query}' - Result description for item {i}") + { + Wrap = true, + IsSubtle = true + } + }); + + var previewCard = new ThumbnailCard() + { + Title = $"Result {i}", + Text = $"This is a preview of result {i} for query '{query}'." + }; + + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard, + Content = card, + Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard, + Content = previewCard + } + }; + + attachments.Add(attachment); + } + + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result + { + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = attachments + } + }; + } + + public static Microsoft.Teams.Api.MessageExtensions.Response HandleCreateCard(JsonElement? data, Microsoft.Teams.Common.Logging.ILogger log) + { + var title = GetJsonValue(data, "title") ?? "Default Title"; + var description = GetJsonValue(data, "description") ?? "Default Description"; + + log.Info($"[CREATE_CARD] Title: {title}, Description: {description}"); + + var card = new Microsoft.Teams.Cards.AdaptiveCard(new List + { + new TextBlock("Custom Card Created") + { + Weight = TextWeight.Bolder, + Size = TextSize.Large, + Color = TextColor.Good + }, + new TextBlock(title) + { + Weight = TextWeight.Bolder, + Size = TextSize.Medium + }, + new TextBlock(description) + { + Wrap = true, + IsSubtle = true + } + }); + + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard, + Content = card, + }; + + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result + { + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List { attachment } + } + }; + } + + public static Microsoft.Teams.Api.MessageExtensions.Response HandleGetMessageDetails(Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, Microsoft.Teams.Common.Logging.ILogger log) + { + var messageText = activity.Value?.MessagePayload?.Body?.Content ?? "No message content"; + var messageId = activity.Value?.MessagePayload?.Id ?? "Unknown"; + + log.Info($"[GET_MESSAGE_DETAILS] Message ID: {messageId}"); + + var card = new Microsoft.Teams.Cards.AdaptiveCard(new List + { + new TextBlock("Message Details") + { + Weight = TextWeight.Bolder, + Size = TextSize.Large, + Color = TextColor.Accent + }, + new TextBlock($"Message ID: {messageId}") + { + Wrap = true + }, + new TextBlock($"Content: {messageText}") + { + Wrap = true + } + }); + + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), + Content = card + }; + + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result + { + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List { attachment } + } + }; + } + + public static Microsoft.Teams.Api.MessageExtensions.Response CreateLinkUnfurlResponse(string url, Microsoft.Teams.Common.Logging.ILogger log) + { + var card = new Microsoft.Teams.Cards.AdaptiveCard(new List + { + new TextBlock("Link Preview") + { + Weight = TextWeight.Bolder, + Size = TextSize.Medium + }, + new TextBlock($"URL: {url}") + { + IsSubtle = true, + Wrap = true + }, + new TextBlock("This is a preview of the linked content generated by the message extension.") + { + Wrap = true, + Size = TextSize.Small + } + }); + + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), + Content = card, + Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard, + Content = new ThumbnailCard + { + Title = "Link Preview", + Text = url + } + } + }; + + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result + { + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List { attachment } + } + }; + } + + public static Microsoft.Teams.Api.MessageExtensions.Response CreateItemSelectionResponse(object? selectedItem, Microsoft.Teams.Common.Logging.ILogger log) + { + var itemJson = JsonSerializer.Serialize(selectedItem); + + var card = new Microsoft.Teams.Cards.AdaptiveCard( + new List + { + new TextBlock("Item Selected") + { + Weight = TextWeight.Bolder, + Size = TextSize.Large, + Color = TextColor.Good + }, + new TextBlock("You selected the following item:") + { + Wrap = true + }, + new TextBlock(itemJson) + { + Wrap = true, + FontType = FontType.Monospace, + Separator = true + } + } + ) + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json" + }; + + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), + Content = card + }; + + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result + { + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List { attachment } + } + }; + } + + public static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorResponse(string message) + { + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result + { + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message, + Text = message + } + }; + } + + public static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorActionResponse(string message) + { + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result + { + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message, + Text = message + } + }; + } + + private static string? GetJsonValue(JsonElement? data, string key) + { + if (data?.ValueKind == JsonValueKind.Object && data.Value.TryGetProperty(key, out var value)) + { + return value.GetString(); + } + return null; + } + } +} diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/MessagingExtensionReminder.csproj b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/MessagingExtensionReminder.csproj index fb02f7a0d6..d924300bab 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/MessagingExtensionReminder.csproj +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/MessagingExtensionReminder.csproj @@ -1,27 +1,30 @@ - + - net6.0 - latest + net10.0 + enable - - - - - - + + + + + + - - - Always + + + + + PreserveNewest + None + + + + PreserveNewest + None - - - - - - + \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Models/CardTaskFetchValue.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Models/CardTaskFetchValue.cs index ad8191b826..c84915f2b5 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Models/CardTaskFetchValue.cs +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Models/CardTaskFetchValue.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft. All rights reserved. // -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace MessagingExtensionReminder.Models { @@ -11,13 +11,13 @@ namespace MessagingExtensionReminder.Models /// public class CardTaskFetchValue { - [JsonProperty("type")] + [JsonPropertyName("type")] public object Type { get; set; } = "task/fetch"; - [JsonProperty("id")] + [JsonPropertyName("id")] public object Id { get; set; } - [JsonProperty("data")] + [JsonPropertyName("data")] public T Data { get; set; } } } \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Models/TaskDetails.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Models/TaskDetails.cs index 8f84c9e4a9..ad607f862e 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Models/TaskDetails.cs +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Models/TaskDetails.cs @@ -2,22 +2,22 @@ // Copyright (c) Microsoft. All rights reserved. // -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace MessagingExtensionReminder.Models { /// /// Location details model class. /// - public class TaskDetails + public class TaskDetails { - [JsonProperty("title")] - public object Title { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - [JsonProperty("description")] - public object Description { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - [JsonProperty("dateTime")] - public object DateTime { get; set; } + [JsonPropertyName("dateTime")] + public DateTime? DateTime { get; set; } } } diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Pages/ScheduleTask.cshtml b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Pages/ScheduleTask.cshtml index c64bcf7713..6a20010e54 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Pages/ScheduleTask.cshtml +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Pages/ScheduleTask.cshtml @@ -59,11 +59,9 @@ } var datetimeLocal = $('#taskdatetime').val() - var datetimeUTC = new Date(datetimeLocal).toUTCString(); - var date = new Date(datetimeUTC); let taskInfo = { title: title.replace(/%20/g," "), - dateTime: date, + dateTime: datetimeLocal, description: description == "" ? $('#taskdescription').val():description.replace(/%20/g," "), }; diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Pages/ScheduleTask.cshtml.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Pages/ScheduleTask.cshtml.cs index cd05b884a0..665c872755 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Pages/ScheduleTask.cshtml.cs +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Pages/ScheduleTask.cshtml.cs @@ -5,8 +5,13 @@ namespace MessagingExtensionReminder.Pages public class ScheduleTaskModel : PageModel { - public void OnGet() + public string? Title { get; set; } + public string? Description { get; set; } + + public void OnGet(string? title, string? description) { + Title = title ?? string.Empty; + Description = description ?? string.Empty; } } } diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Program.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Program.cs index 62210b21f5..940ec32dcb 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Program.cs +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Program.cs @@ -1,25 +1,79 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// -// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.14.0 -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using MessagingExtensionReminder; +using MessagingExtensionReminder.Controllers; +using MessagingExtensionReminder.Models; +using MessagingExtensionReminder.Services; +using Azure.Core; +using Azure.Identity; +using Microsoft.Teams.Api.Auth; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Extensions; +using Microsoft.Teams.Common.Http; +using Microsoft.Teams.Plugins.AspNetCore.Extensions; +using System.Collections.Concurrent; -namespace MessagingExtensionReminder +var builder = WebApplication.CreateBuilder(args); +var config = builder.Configuration.Get(); + +Func> createTokenFactory = async (string[] scopes, string? tenantId) => { - public class Program + var clientId = config.Teams.ClientId; + + var managedIdentityCredential = new ManagedIdentityCredential(clientId); + var tokenRequestContext = new TokenRequestContext(scopes, tenantId: tenantId); + var accessToken = await managedIdentityCredential.GetTokenAsync(tokenRequestContext); + + return new TokenResponse { - public static void Main(string[] args) + TokenType = "Bearer", + AccessToken = accessToken.Token, + }; +}; + +var appBuilder = App.Builder(); + +if (config.Teams.BotType == "UserAssignedMsi") +{ + appBuilder.AddCredentials(new TokenCredentials( + config.Teams.ClientId ?? string.Empty, + async (tenantId, scopes) => { - CreateHostBuilder(args).Build().Run(); + return await createTokenFactory(scopes, tenantId); } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } + )); } + +// Register singleton services for state management +builder.Services.AddSingleton(new ConcurrentDictionary()); +builder.Services.AddSingleton(new ConcurrentDictionary>()); + +// Add controllers for API endpoints +builder.Services.AddControllers(); + +// Register the Teams controller with explicit dependencies +builder.Services.AddSingleton(sp => new Controller( + sp.GetRequiredService>>(), + sp.GetRequiredService>(), + builder.Configuration +)); + +// Register the background service for sending reminders +builder.Services.AddHostedService(); +builder.Services.AddRazorPages(); +builder.AddTeams(appBuilder); + +var app = builder.Build(); + +// Enable static files from wwwroot +app.UseStaticFiles(); + +// Map controllers for API endpoints +app.MapControllers(); + +// Map Razor Pages +app.MapRazorPages(); + +app.UseTeams(); +app.Run(); \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Properties/launchSettings.json b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Properties/launchSettings.json index ff9d8fe153..1b0aaa59f3 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Properties/launchSettings.json +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Properties/launchSettings.json @@ -1,9 +1,22 @@ -{ +{ "profiles": { + // Debug project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground": { + "commandName": "Project", + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Playground", + "TEAMSFX_NOTIFICATION_STORE_FILENAME": ".notification.playgroundstore.json", + "UPDATE_TEAMS_APP": "false" + }, + "hotReloadProfile": "aspnetcore" + }, + // Debug project within Teams "Start Project": { "commandName": "Project", "dotnetRunMessages": true, - "applicationUrl": "https://localhost:7130;http://localhost:5130", + "applicationUrl": "http://localhost:5130", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Services/ReminderBackgroundService.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Services/ReminderBackgroundService.cs new file mode 100644 index 0000000000..2b56be9f87 --- /dev/null +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Services/ReminderBackgroundService.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using MessagingExtensionReminder.Models; +using Microsoft.Teams.Api; +using System.Collections.Concurrent; + +namespace MessagingExtensionReminder.Services +{ + public class ReminderBackgroundService : BackgroundService + { + private readonly ConcurrentDictionary _conversationReferences; + private readonly ConcurrentDictionary> _taskDetails; + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + public ReminderBackgroundService( + ConcurrentDictionary conversationReferences, + ConcurrentDictionary> taskDetails, + IServiceProvider serviceProvider, + ILogger logger) + { + _conversationReferences = conversationReferences; + _taskDetails = taskDetails; + _serviceProvider = serviceProvider; + _logger = logger; + } + + // Runs the background service and checks for reminders every minute + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + try + { + await CheckAndSendReminders(stoppingToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occurred while checking reminders"); + } + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + } + } + + // Checks for due tasks and sends reminder messages to users + private async Task CheckAndSendReminders(CancellationToken stoppingToken) + { + var now = DateTimeOffset.Now; + if (!_taskDetails.TryGetValue("taskDetails", out var tasks) || tasks == null || tasks.Count == 0) + { + return; + } + foreach (var task in tasks) + { + var timeDifference = Math.Abs((task.DateTime - now).TotalMinutes); + if (timeDifference < 1) + { + foreach (var conversationRef in _conversationReferences) + { + var conversationReference = conversationRef.Value; + try + { + await SendProactiveReminder(conversationReference, task, stoppingToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to send reminder"); + } + } + } + } + } + + // Sends a proactive reminder message to a specific conversation + private async Task SendProactiveReminder(ConversationReference conversationReference, SaveTaskDetail task, CancellationToken stoppingToken) + { + using var scope = _serviceProvider.CreateScope(); + var app = scope.ServiceProvider.GetRequiredService(); + var reminderMessage = $"**Reminder for scheduled task!**\n\n" + + $"Task title: {System.Net.WebUtility.UrlDecode(task.Title)}\n\n" + + $"Task description: {System.Net.WebUtility.UrlDecode(task.Description)}"; + await app.Send(conversationReference.Conversation.Id, reminderMessage); + } + } +} diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Startup.cs b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Startup.cs deleted file mode 100644 index e9d6bb0f78..0000000000 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/Startup.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// -// Generated with Bot Builder V4 SDK Template for Visual Studio v4.14.0 - -using MessagingExtensionReminder.Bots; -using MessagingExtensionReminder.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Schema; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace MessagingExtensionReminder -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers().AddNewtonsoftJson(); - - services.AddHttpClient().AddControllers().AddNewtonsoftJson(); - services.AddRazorPages(); - - // Create the Bot Framework Adapter with error handling enabled. - services.AddSingleton(); - - // Create a global hashset for our ConversationReferences - services.AddSingleton>(); - - // Create a global hashset for our save task details - services.AddSingleton>>(); - - // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) - services.AddSingleton(); - - // Create the Conversation state. (Used by the Dialog system itself.) - services.AddSingleton(); - - // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. - services.AddTransient(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseDefaultFiles(); - app.UseStaticFiles(); - app.UseWebSockets() - .UseRouting() - .UseAuthorization() - .UseEndpoints(endpoints => - { - endpoints.MapControllers(); - endpoints.MapRazorPages(); - }); - } - } -} \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.Development.json b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.Development.json index b49abfc201..02ebd04fb1 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.Development.json +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.Development.json @@ -1,9 +1,20 @@ { - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } - } + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "", + "TenantId": "" + }, + "ApplicationBaseUrl": "" +} \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.Playground.json b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.Playground.json new file mode 100644 index 0000000000..0497106b3c --- /dev/null +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.Playground.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "TenantId": "", + "BotType": "" + } +} \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.json b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.json index f491de3503..cfdca4a79b 100644 --- a/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.json +++ b/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/appsettings.json @@ -1,5 +1,19 @@ { - "MicrosoftAppId": "{{Microsoft-App-Id}}", - "MicrosoftAppPassword": "{{ Microsoft-App-Password}}", - "ApplicationBaseUrl": "{{ Application Base Url }}" + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "ApplicationBaseUrl": "", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "" + } } \ No newline at end of file diff --git a/samples/msgext-message-reminder/csharp/README.md b/samples/msgext-message-reminder/csharp/README.md index 50ae73e6bc..46c161992f 100644 --- a/samples/msgext-message-reminder/csharp/README.md +++ b/samples/msgext-message-reminder/csharp/README.md @@ -67,16 +67,16 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool > NOTE: When you create your app registration, you will create an App ID and App password - make sure you keep these for later. 3. Setup NGROK - - Run ngrok - point to port 3978 + - Run ngrok - point to port 5130 ```bash - ngrok http 3978 --host-header="localhost:3978" + ngrok http 5130 --host-header="localhost:5130" ``` Alternatively, you can also use the `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: ```bash - devtunnel host -p 3978 --allow-anonymous + devtunnel host -p 5130 --allow-anonymous ``` ## 2) App Registration @@ -104,10 +104,12 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool git clone https://github.com/OfficeDev/Microsoft-Teams-Samples.git ``` - Modify the `appsettings.json` and fill in the following details: - - `{{Microsoft-App-Id}}` - Generated from Step 1 is the application app id - - `{{ Microsoft-App-Password}}` - Generated from Step 1, also referred to as Client secret - - `{{ Application Base Url }}` - Your application's base url. E.g. https://12345.ngrok-free.app if you are using ngrok and if you are using dev tunnels, your URL will be like: https://12345.devtunnels.ms. + Modify the `appsettings.Development.json` and fill in the following details: + - `ClientId` - Generated from Step 1 is the application app id + - `ClientSecret` - Generated from Step 1, also referred to as Client secret + - `TenantId` - Directory (tenant) ID from Step 1 + - `BotType` - Set to "MultiTenant" or "SingleTenant" based on your app registration + - `ApplicationBaseUrl` - Your application's base url. E.g. https://12345.ngrok-free.app if you are using ngrok and if you are using dev tunnels, your URL will be like: https://12345.devtunnels.ms. - From a terminal, navigate to `samples/msgext-message-reminder/csharp` @@ -122,7 +124,7 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool 5. Setup Manifest for Teams - __*This step is specific to Teams.*__ - - **Edit** the `manifest.json` contained in the ./appPackage folder to replace your Microsoft App Id (that was created when you registered your app registration earlier) *everywhere* you see the place holder string `{{Microsoft-App-Id}}` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Edit** the `manifest.json` contained in the ./appPackage folder to replace your Microsoft App Id (that was created when you registered your app registration earlier) *everywhere* you see the place holder string `{{TEAMS_ID}}` and `{{BOT_ID}}` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) - **Edit** the `manifest.json` for `validDomains` and replace `{{domain-name}}` with base Url of your domain. E.g. if you are using ngrok it would be `https://1234.ngrok-free.app` then your domain-name will be `1234.ngrok-free.app` and if you are using dev tunnels then your domain will be like: `12345.devtunnels.ms`. - **Zip** up the contents of the `appPackage` folder to create a `manifest.zip` (Make sure that zip file does not contains any subfolder otherwise you will get error while uploading your .zip package) @@ -132,7 +134,6 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool - Go to your project directory, the ./appPackage folder, select the zip folder, and choose Open. - Select Add in the pop-up dialog box. Your app is uploaded to Teams. -**Note**: If you are facing any issue in your app, please uncomment [this](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/msgext-message-reminder/csharp/MessagingExtensionReminder/AdapterWithErrorHandler.cs#L30) line and put your debugger for local debug. ## Running the sample @@ -174,7 +175,7 @@ To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](htt ## Further reading - [Messaging Extension](https://learn.microsoft.com/microsoftteams/platform/messaging-extensions/how-to/action-commands/define-action-command) -- [Bot Framework Documentation](https://docs.botframework.com) +- [Azure AI Bot Service Documentation](https://docs.botframework.com) - [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) - [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) - [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/tab-conversations/csharp/M365Agent/.gitignore b/samples/tab-conversations/csharp/M365Agent/.gitignore new file mode 100644 index 0000000000..c5cae9258c --- /dev/null +++ b/samples/tab-conversations/csharp/M365Agent/.gitignore @@ -0,0 +1,10 @@ +# TeamsFx files +build +appPackage/build +env/.env.*.user +env/.env.local +appsettings.Development.json +.deployment + +# User-specific files +*.user diff --git a/samples/bot-auth0-adaptivecard/csharp/M365Agent/M365Agent.ttkproj b/samples/tab-conversations/csharp/M365Agent/M365Agent.atkproj similarity index 63% rename from samples/bot-auth0-adaptivecard/csharp/M365Agent/M365Agent.ttkproj rename to samples/tab-conversations/csharp/M365Agent/M365Agent.atkproj index 1caf99b9bc..0965efc8d5 100644 --- a/samples/bot-auth0-adaptivecard/csharp/M365Agent/M365Agent.ttkproj +++ b/samples/tab-conversations/csharp/M365Agent/M365Agent.atkproj @@ -1,10 +1,10 @@ - + - 42a0237c-30e0-42ae-b6ed-50f318517e77 + b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf - + diff --git a/samples/tab-conversations/csharp/M365Agent/appPackage/color.png b/samples/tab-conversations/csharp/M365Agent/appPackage/color.png index b8cf81afbe..01aa37e347 100644 Binary files a/samples/tab-conversations/csharp/M365Agent/appPackage/color.png and b/samples/tab-conversations/csharp/M365Agent/appPackage/color.png differ diff --git a/samples/tab-conversations/csharp/M365Agent/appPackage/manifest.json b/samples/tab-conversations/csharp/M365Agent/appPackage/manifest.json index 05bcfaf23c..e46fc7c088 100644 --- a/samples/tab-conversations/csharp/M365Agent/appPackage/manifest.json +++ b/samples/tab-conversations/csharp/M365Agent/appPackage/manifest.json @@ -1,42 +1,56 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json", - "manifestVersion": "1.19", - "version": "1.0.0", - "id": "${{TEAMS_APP_ID}}", - "developer": { - "name": "Microsoft", - "websiteUrl": "https://www.microsoft.com", - "privacyUrl": "https://www.microsoft.com/privacy", - "termsOfUseUrl": "https://www.microsoft.com/termsofuse" - }, - "name": { - "short": "Conversational Tab", - "full": "Conversational Tab" - }, - "description": { - "short": "Sample Teams app demonstrating conversational tabs for user interactions.", - "full": "This Microsoft Teams sample app showcases how to create conversational tabs for users to discuss sub-entities within a tab, enhancing collaboration and interaction." - }, - "icons": { - "outline": "outline.png", - "color": "color.png" - }, - "accentColor": "#60A18E", - "configurableTabs": [ - { - "configurationUrl": "https://${{TAB_DOMAIN}}/configure", - "canUpdateConfiguration": true, - "scopes": [ - "team" - ], - "context": [ - "channelTab" - ] - } - ], - "permissions": [ "identity", "messageTeamMembers" ], - "validDomains": [ - "*.ngrok-free.app", - "${{TAB_DOMAIN}}" - ] +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json", + "manifestVersion": "1.23", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "TabConversation${{APP_NAME_SUFFIX}}", + "full": "Conversational Tab Sample" + }, + "description": { + "short": "Sample Teams app demonstrating conversational tabs for user interactions.", + "full": "This Microsoft Teams sample app showcases how to create conversational tabs for users to discuss sub-entities within a tab, enhancing collaboration and interaction." + }, + "accentColor": "#60A18E", + "configurableTabs": [ + { + "configurationUrl": "https://${{BOT_DOMAIN}}/configure", + "canUpdateConfiguration": true, + "scopes": [ + "team" + ], + "context": [ + "channelTab" + ] + } + ], + "staticTabs": [ + { + "entityId": "index", + "name": "Home", + "contentUrl": "https://${{BOT_DOMAIN}}/index", + "websiteUrl": "https://${{BOT_DOMAIN}}/index", + "scopes": [ + "personal" + ] + } + ], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [ + "${{BOT_DOMAIN}}", + "*.ngrok-free.app" + ] } \ No newline at end of file diff --git a/samples/tab-conversations/csharp/M365Agent/appPackage/outline.png b/samples/tab-conversations/csharp/M365Agent/appPackage/outline.png index 2c3bf6fa65..f7a4c86447 100644 Binary files a/samples/tab-conversations/csharp/M365Agent/appPackage/outline.png and b/samples/tab-conversations/csharp/M365Agent/appPackage/outline.png differ diff --git a/samples/tab-conversations/csharp/M365Agent/env/.env.dev b/samples/tab-conversations/csharp/M365Agent/env/.env.dev new file mode 100644 index 0000000000..7273ce04fb --- /dev/null +++ b/samples/tab-conversations/csharp/M365Agent/env/.env.dev @@ -0,0 +1,16 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +TEAMS_APP_ID= + +# Tab domain (will be set during provision) +TAB_DOMAIN= \ No newline at end of file diff --git a/samples/tab-conversations/csharp/M365Agent/env/.env.local b/samples/tab-conversations/csharp/M365Agent/env/.env.local index 5edb55f227..b2728d221e 100644 --- a/samples/tab-conversations/csharp/M365Agent/env/.env.local +++ b/samples/tab-conversations/csharp/M365Agent/env/.env.local @@ -2,12 +2,15 @@ # Built-in environment variables TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + # Generated during provision, you can also add your own variables. TEAMS_APP_ID= -TAB_ENDPOINT= - -APP_NAME_SUFFIX=local TAB_DOMAIN= TEAMS_APP_TENANT_ID= -TEAMSFX_M365_USER_NAME= \ No newline at end of file + +TEAMSFX_M365_USER_NAME= + +BOT_ENDPOINT= +BOT_DOMAIN= \ No newline at end of file diff --git a/samples/tab-conversations/csharp/M365Agent/infra/azure.bicep b/samples/tab-conversations/csharp/M365Agent/infra/azure.bicep new file mode 100644 index 0000000000..658e412a21 --- /dev/null +++ b/samples/tab-conversations/csharp/M365Agent/infra/azure.bicep @@ -0,0 +1,86 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param identityName string = resourceBaseName +param location string = resourceGroup().location + +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + location: location + name: identityName +} + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' + } + { + name: 'Teams__ClientId' + value: identity.properties.clientId + } + { + name: 'Teams__TenantId' + value: identity.properties.tenantId + } + { + name: 'Teams__BotType' + value: 'UserAssignedMsi' + } + ] + ftpsState: 'FtpsOnly' + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${identity.id}': {} + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + identityClientId: identity.properties.clientId + identityResourceId: identity.id + identityTenantId: identity.properties.tenantId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName +output BOT_ID string = identity.properties.clientId +output BOT_TENANT_ID string = identity.properties.tenantId diff --git a/samples/tab-conversations/csharp/M365Agent/infra/azure.parameters.json b/samples/tab-conversations/csharp/M365Agent/infra/azure.parameters.json new file mode 100644 index 0000000000..98c5bb7498 --- /dev/null +++ b/samples/tab-conversations/csharp/M365Agent/infra/azure.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "TabConversation" + } + } + } \ No newline at end of file diff --git a/samples/tab-conversations/csharp/M365Agent/infra/botRegistration/azurebot.bicep b/samples/tab-conversations/csharp/M365Agent/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..a5a27b8fe4 --- /dev/null +++ b/samples/tab-conversations/csharp/M365Agent/infra/botRegistration/azurebot.bicep @@ -0,0 +1,42 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param identityResourceId string +param identityClientId string +param identityTenantId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: identityClientId + msaAppMSIResourceId: identityResourceId + msaAppTenantId:identityTenantId + msaAppType:'UserAssignedMSI' + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/samples/tab-conversations/csharp/M365Agent/infra/botRegistration/readme.md b/samples/tab-conversations/csharp/M365Agent/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/tab-conversations/csharp/M365Agent/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/samples/tab-conversations/csharp/M365Agent/launchSettings.json b/samples/tab-conversations/csharp/M365Agent/launchSettings.json index 8c76c70d9e..2af8ce7a8a 100644 --- a/samples/tab-conversations/csharp/M365Agent/launchSettings.json +++ b/samples/tab-conversations/csharp/M365Agent/launchSettings.json @@ -1,17 +1,25 @@ { - "profiles": { - // Debug project within Teams - "Microsoft Teams (browser)": { - "commandName": "Project", - "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" - } - }, - // Launch project within Teams without prepare app dependencies - "Microsoft Teams (browser) (skip update app)": { - "commandName": "Project", - "environmentVariables": { - "UPDATE_TEAMS_APP": "false" - }, - "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" - } + "profiles": { + // Launch project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground (browser)": { + "commandName": "Project", + "environmentVariables": { + "UPDATE_TEAMS_APP": "false", + "M365_AGENTS_PLAYGROUND_TARGET_SDK": "teams-ai-v2-dotnet" + }, + "launchTestTool": true, + "launchUrl": "http://localhost:56150", + }, + // Launch project within Teams + "Microsoft Teams (browser)": { + "commandName": "Project", + "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}", + }, + // Launch project within Teams without prepare app dependencies + "Microsoft Teams (browser) (skip update app)": { + "commandName": "Project", + "environmentVariables": { "UPDATE_TEAMS_APP": "false" }, + "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" + }, + } } \ No newline at end of file diff --git a/samples/tab-conversations/csharp/M365Agent/m365agents.local.yml b/samples/tab-conversations/csharp/M365Agent/m365agents.local.yml index 57fe83290e..990c987ff2 100644 --- a/samples/tab-conversations/csharp/M365Agent/m365agents.local.yml +++ b/samples/tab-conversations/csharp/M365Agent/m365agents.local.yml @@ -1,29 +1,24 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.2 - -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:tab-conversations-csharp +version: v1.11 provision: - # Set TAB_DOMAIN and TAB_ENDPOINT for local launch - - uses: script - with: - run: - echo "::set-teamsfx-env TAB_DOMAIN=localhost:44302"; - echo "::set-teamsfx-env TAB_ENDPOINT=https://localhost:44302"; - # Creates a Teams app - uses: teamsApp/create with: # Teams app name - name: tab-conversations${{APP_NAME_SUFFIX}} + name: TabConversation${{APP_NAME_SUFFIX}} # Write the information of created resources into environment file for # the specified environment variable(s). - writeToEnvironmentFile: + writeToEnvironmentFile: teamsAppId: TEAMS_APP_ID + # Set TAB_DOMAIN for local development + - uses: script + with: + run: echo "::set-teamsfx-env TAB_DOMAIN=localhost:5130"; + # Validate using manifest schema - uses: teamsApp/validateManifest with: @@ -35,16 +30,17 @@ provision: # Path to manifest template manifestPath: ./appPackage/manifest.json outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + outputFolder: ./appPackage/build # Validate app package using validation rules - uses: teamsApp/validateAppPackage with: # Relative path to this file. This is the path for built zip file. appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in # Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. - uses: teamsApp/update with: # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip \ No newline at end of file + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip diff --git a/samples/tab-conversations/csharp/M365Agent/m365agents.yml b/samples/tab-conversations/csharp/M365Agent/m365agents.yml index 7f3eed5ff8..a00386dc29 100644 --- a/samples/tab-conversations/csharp/M365Agent/m365agents.yml +++ b/samples/tab-conversations/csharp/M365Agent/m365agents.yml @@ -1,9 +1,88 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.2 +version: v1.9 -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:tab-conversations-csharp +environmentFolderPath: ./env -environmentFolderPath: ./env \ No newline at end of file +# Triggered when 'teamsapp provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: TabConversation${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + - uses: cli/runDotnetCommand + with: + args: publish --configuration Release --runtime win-x86 --self-contained + TabConversation.csproj + workingDirectory: ../TabConversation + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: bin/Release/net10.0/win-x86/publish + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + workingDirectory: ../TabConversation diff --git a/samples/tab-conversations/csharp/README.md b/samples/tab-conversations/csharp/README.md index 1bca77ac2d..cdf746d10c 100644 --- a/samples/tab-conversations/csharp/README.md +++ b/samples/tab-conversations/csharp/README.md @@ -31,7 +31,7 @@ Please find below demo manifest which is deployed on Microsoft Azure and you can ## Prerequisites -- [.NET Core SDK](https://dotnet.microsoft.com/download) version 6.0 +- [.NET Core SDK](https://dotnet.microsoft.com/download) version 10.0 ```bash # determine dotnet version @@ -74,16 +74,16 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool * Click on Add permissions. Please make sure to grant the admin consent for the required permissions. 2. Setup NGROK - - Run ngrok - point to port 3978 + - Run ngrok - point to port 5130 ```bash - ngrok http 3978 --host-header="localhost:3978" + ngrok http 5130 --host-header="localhost:5130" ``` Alternatively, you can also use the `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: ```bash - devtunnel host -p 3978 --allow-anonymous + devtunnel host -p 5130 --allow-anonymous ``` 3. Setup for code @@ -110,8 +110,8 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool 4. Setup Manifest for Teams - __*This step is specific to Teams.*__ - - **Edit** the `manifest.json` contained in the ./appPackage folder to replace your Microsoft App Id (that was created when you registered your app registration earlier) *everywhere* you see the place holder string `{{Microsoft-App-Id}}` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) - - **Edit** the `manifest.json` for `validDomains` and replace `${{TAB_DOMAIN}}` with base Url of your domain. E.g. if you are using ngrok it would be `https://1234.ngrok-free.app` then your domain-name will be `1234.ngrok-free.app` and if you are using dev tunnels then your domain will be like: `12345.devtunnels.ms`. + - **Edit** the `manifest.json` contained in the ./appPackage folder to replace your Microsoft App Id (that was created when you registered your app registration earlier) *everywhere* you see the place holder string `{{TEAMS_APP_ID}}` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Edit** the `manifest.json` for `validDomains` and replace `${{BOT_DOMAIN}}` with base Url of your domain. E.g. if you are using ngrok it would be `https://1234.ngrok-free.app` then your domain-name will be `1234.ngrok-free.app` and if you are using dev tunnels then your domain will be like: `12345.devtunnels.ms`. - **Zip** up the contents of the `appPackage` folder to create a `manifest.zip` (Make sure that zip file does not contains any subfolder otherwise you will get error while uploading your .zip package) - Upload the manifest.zip to Teams (in the Apps view click "Upload a custom app") diff --git a/samples/tab-conversations/csharp/TabConversation.sln b/samples/tab-conversations/csharp/TabConversation.sln deleted file mode 100644 index 0b9f5e030f..0000000000 --- a/samples/tab-conversations/csharp/TabConversation.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.10.34814.14 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TabConversation", "TabConversation\TabConversation.csproj", "{801722A2-02B1-4A1E-9639-4358D5D87D98}" -EndProject -Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{696F2F0A-C3AA-4FF8-8D35-98A5F4CE4960}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3EB8E93C-B90F-4921-9826-F80C58F428B9}" - ProjectSection(SolutionItems) = preProject - TabConversation.slnLaunch.user = TabConversation.slnLaunch.user - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {801722A2-02B1-4A1E-9639-4358D5D87D98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {801722A2-02B1-4A1E-9639-4358D5D87D98}.Debug|Any CPU.Build.0 = Debug|Any CPU - {801722A2-02B1-4A1E-9639-4358D5D87D98}.Release|Any CPU.ActiveCfg = Release|Any CPU - {801722A2-02B1-4A1E-9639-4358D5D87D98}.Release|Any CPU.Build.0 = Release|Any CPU - {696F2F0A-C3AA-4FF8-8D35-98A5F4CE4960}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {696F2F0A-C3AA-4FF8-8D35-98A5F4CE4960}.Debug|Any CPU.Build.0 = Debug|Any CPU - {696F2F0A-C3AA-4FF8-8D35-98A5F4CE4960}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {696F2F0A-C3AA-4FF8-8D35-98A5F4CE4960}.Release|Any CPU.ActiveCfg = Release|Any CPU - {696F2F0A-C3AA-4FF8-8D35-98A5F4CE4960}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {67D8F260-C910-4B01-90CD-9DF5C8C4A6DC} - EndGlobalSection -EndGlobal diff --git a/samples/tab-conversations/csharp/TabConversation.slnLaunch.user b/samples/tab-conversations/csharp/TabConversation.slnLaunch.user index 45fd9a700f..9300c82b87 100644 --- a/samples/tab-conversations/csharp/TabConversation.slnLaunch.user +++ b/samples/tab-conversations/csharp/TabConversation.slnLaunch.user @@ -1,31 +1,52 @@ [ { - "Name": "Microsoft Teams (browser)", + "Name": "Microsoft 365 Agents Playground (browser)", "Projects": [ + { + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft 365 Agents Playground (browser)" + }, { "Path": "TabConversation\\TabConversation.csproj", + "Name": "TabConversation\\TabConversation.csproj", "Action": "Start", - "DebugTarget": "Start Project" - }, + "DebugTarget": "Microsoft 365 Agents Playground" + } + ] + }, + { + "Name": "Microsoft Teams (browser)", + "Projects": [ { - "Path": "M365Agent\\M365Agent.ttkproj", + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", "Action": "StartWithoutDebugging", "DebugTarget": "Microsoft Teams (browser)" + }, + { + "Path": "TabConversation\\TabConversation.csproj", + "Name": "TabConversation\\TabConversation.csproj", + "Action": "Start", + "DebugTarget": "Start Project" } ] }, { "Name": "Microsoft Teams (browser) (skip update app)", "Projects": [ + { + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft Teams (browser) (skip update app)" + }, { "Path": "TabConversation\\TabConversation.csproj", + "Name": "TabConversation\\TabConversation.csproj", "Action": "Start", "DebugTarget": "Start Project" - }, - { - "Path": "M365Agent\\M365Agent.ttkproj", - "Action": "StartWithoutDebugging", - "DebugTarget": "Microsoft Teams (browser) (skip update app)" } ] } diff --git a/samples/tab-conversations/csharp/TabConversation.slnx b/samples/tab-conversations/csharp/TabConversation.slnx new file mode 100644 index 0000000000..c962b6bb07 --- /dev/null +++ b/samples/tab-conversations/csharp/TabConversation.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/samples/tab-conversations/csharp/TabConversation/.gitignore b/samples/tab-conversations/csharp/TabConversation/.gitignore index 4596bae4d8..77c7154916 100644 --- a/samples/tab-conversations/csharp/TabConversation/.gitignore +++ b/samples/tab-conversations/csharp/TabConversation/.gitignore @@ -5,6 +5,7 @@ env/.env.*.user env/.env.local appsettings.Development.json .deployment +appsettings.Playground.json # User-specific files *.user @@ -23,3 +24,7 @@ bld/ # Notification local store .notification.localstore.json +.notification.playgroundstore.json + +# devTools +devTools/ \ No newline at end of file diff --git a/samples/tab-conversations/csharp/TabConversation/Controllers/Controller.cs b/samples/tab-conversations/csharp/TabConversation/Controllers/Controller.cs new file mode 100644 index 0000000000..c711142384 --- /dev/null +++ b/samples/tab-conversations/csharp/TabConversation/Controllers/Controller.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Teams.Apps.Annotations; + +namespace TabConversation.Controllers +{ + [TeamsController] + public class Controller() + { + } +} diff --git a/samples/tab-conversations/csharp/TabConversation/Pages/Index.cshtml b/samples/tab-conversations/csharp/TabConversation/Pages/Index.cshtml index b39a396939..e16ad266b0 100644 --- a/samples/tab-conversations/csharp/TabConversation/Pages/Index.cshtml +++ b/samples/tab-conversations/csharp/TabConversation/Pages/Index.cshtml @@ -1,4 +1,7 @@ -@page + + +@page

Welcome

diff --git a/samples/tab-conversations/csharp/TabConversation/Pages/conversation-tab.cshtml b/samples/tab-conversations/csharp/TabConversation/Pages/conversationTab.cshtml similarity index 82% rename from samples/tab-conversations/csharp/TabConversation/Pages/conversation-tab.cshtml rename to samples/tab-conversations/csharp/TabConversation/Pages/conversationTab.cshtml index 1c9fc75e72..5aeb0f808e 100644 --- a/samples/tab-conversations/csharp/TabConversation/Pages/conversation-tab.cshtml +++ b/samples/tab-conversations/csharp/TabConversation/Pages/conversationTab.cshtml @@ -1,11 +1,11 @@ -@page "/conversationTab" +@page "/conversationTab" - - + + @@ -82,22 +82,22 @@ Start Conversation To open a conversation - + Close Conversation To close the conversation view - + Continue Conversation To continue a conversation. - + Deeplink to conversation Redirect to channel conversation. - +
@@ -105,4 +105,4 @@
- \ No newline at end of file + diff --git a/samples/tab-conversations/csharp/TabConversation/Program.cs b/samples/tab-conversations/csharp/TabConversation/Program.cs index 8be42dfe7d..66692f1f3c 100644 --- a/samples/tab-conversations/csharp/TabConversation/Program.cs +++ b/samples/tab-conversations/csharp/TabConversation/Program.cs @@ -1,26 +1,34 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using TabConversation.Controllers; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Extensions; +using Microsoft.Teams.Plugins.AspNetCore.Extensions; -namespace TabConversation -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } +var builder = WebApplication.CreateBuilder(args); + +var appBuilder = App.Builder(); + +builder.Services.AddSingleton(); +builder.Services.AddRazorPages(); +builder.AddTeams(appBuilder); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); } +else +{ + app.UseExceptionHandler("/Error"); + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseDefaultFiles(); +app.UseStaticFiles(); +app.UseRouting(); +app.UseAuthorization(); +app.MapRazorPages(); +app.UseTeams(); + +app.Run(); \ No newline at end of file diff --git a/samples/tab-conversations/csharp/TabConversation/Properties/launchSettings.json b/samples/tab-conversations/csharp/TabConversation/Properties/launchSettings.json index 51e0f32023..3572a7a03f 100644 --- a/samples/tab-conversations/csharp/TabConversation/Properties/launchSettings.json +++ b/samples/tab-conversations/csharp/TabConversation/Properties/launchSettings.json @@ -1,13 +1,26 @@ { - "profiles": { - "Start Project": { - "commandName": "Project", - "dotnetRunMessages": true, - "applicationUrl": "https://localhost:44302;http://localhost:3978", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "hotReloadProfile": "aspnetcore" - } - } + "profiles": { + // Debug project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground": { + "commandName": "Project", + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Playground", + "TEAMSFX_NOTIFICATION_STORE_FILENAME": ".notification.playgroundstore.json", + "UPDATE_TEAMS_APP": "false" + }, + "hotReloadProfile": "aspnetcore" + }, + // Debug project within Teams + "Start Project": { + "commandName": "Project", + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "hotReloadProfile": "aspnetcore" + }, + } } \ No newline at end of file diff --git a/samples/tab-conversations/csharp/TabConversation/Startup.cs b/samples/tab-conversations/csharp/TabConversation/Startup.cs deleted file mode 100644 index 7c844d3e06..0000000000 --- a/samples/tab-conversations/csharp/TabConversation/Startup.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Hosting; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace TabConversation -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddRazorPages(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseDefaultFiles() - .UseStaticFiles() - .UseWebSockets() - .UseRouting() - .UseAuthorization() - .UseEndpoints(endpoints => - { - endpoints.MapRazorPages(); - }); - } - } -} diff --git a/samples/tab-conversations/csharp/TabConversation/TabConversation.csproj b/samples/tab-conversations/csharp/TabConversation/TabConversation.csproj index dbc869398b..cd0bdcb565 100644 --- a/samples/tab-conversations/csharp/TabConversation/TabConversation.csproj +++ b/samples/tab-conversations/csharp/TabConversation/TabConversation.csproj @@ -1,12 +1,30 @@ - net6.0 - false + net10.0 + enable - + + + + + + - + + + + + PreserveNewest + None + + + + PreserveNewest + None + + +
\ No newline at end of file diff --git a/samples/tab-conversations/csharp/TabConversation/appsettings.Development.json b/samples/tab-conversations/csharp/TabConversation/appsettings.Development.json index 8983e0fc1c..f225c8442d 100644 --- a/samples/tab-conversations/csharp/TabConversation/appsettings.Development.json +++ b/samples/tab-conversations/csharp/TabConversation/appsettings.Development.json @@ -1,9 +1,19 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "", + "TenantId": "" + } +} \ No newline at end of file diff --git a/samples/tab-conversations/csharp/TabConversation/appsettings.Playground.json b/samples/tab-conversations/csharp/TabConversation/appsettings.Playground.json new file mode 100644 index 0000000000..0497106b3c --- /dev/null +++ b/samples/tab-conversations/csharp/TabConversation/appsettings.Playground.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "TenantId": "", + "BotType": "" + } +} \ No newline at end of file diff --git a/samples/tab-conversations/csharp/TabConversation/appsettings.json b/samples/tab-conversations/csharp/TabConversation/appsettings.json index d9d9a9bff6..9e3379db53 100644 --- a/samples/tab-conversations/csharp/TabConversation/appsettings.json +++ b/samples/tab-conversations/csharp/TabConversation/appsettings.json @@ -2,9 +2,17 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" } }, - "AllowedHosts": "*" -} + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "" + } +} \ No newline at end of file diff --git a/samples/tab-conversations/csharp/TabConversation/wwwroot/default.htm b/samples/tab-conversations/csharp/TabConversation/wwwroot/default.htm deleted file mode 100644 index 29323af45e..0000000000 --- a/samples/tab-conversations/csharp/TabConversation/wwwroot/default.htm +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - - MeetingContext - - - - - -
-
-
-
MeetingContext Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/samples/tab-conversations/csharp/TabConversation/static/styles/custom.css b/samples/tab-conversations/csharp/TabConversation/wwwroot/styles/custom.css similarity index 79% rename from samples/tab-conversations/csharp/TabConversation/static/styles/custom.css rename to samples/tab-conversations/csharp/TabConversation/wwwroot/styles/custom.css index f60feebacb..1ab1891763 100644 --- a/samples/tab-conversations/csharp/TabConversation/static/styles/custom.css +++ b/samples/tab-conversations/csharp/TabConversation/wwwroot/styles/custom.css @@ -5,11 +5,14 @@ html, body, div.surface, div.panel { div.panel { padding: 15px; + max-width: 600px; } + table { font-family: arial, sans-serif; border-collapse: collapse; width: 100%; + max-width: 500px; } td, th { @@ -20,6 +23,7 @@ td, th { } img { - margin-left:6rem; + margin-left:2rem; width:20px; + cursor: pointer; } \ No newline at end of file diff --git a/samples/tab-conversations/csharp/TabConversation/static/styles/msteams-16.css b/samples/tab-conversations/csharp/TabConversation/wwwroot/styles/msteams-16.css similarity index 100% rename from samples/tab-conversations/csharp/TabConversation/static/styles/msteams-16.css rename to samples/tab-conversations/csharp/TabConversation/wwwroot/styles/msteams-16.css diff --git a/samples/tab-conversations/csharp/assets/sample.json b/samples/tab-conversations/csharp/assets/sample.json index 8745eddd3c..9c671d4244 100644 --- a/samples/tab-conversations/csharp/assets/sample.json +++ b/samples/tab-conversations/csharp/assets/sample.json @@ -9,7 +9,7 @@ "This Microsoft Teams sample app showcases how to create conversational tabs for users to discuss sub-entities within a tab, enhancing collaboration and interaction." ], "creationDateTime": "2021-06-10", - "updateDateTime": "2024-10-15", + "updateDateTime": "2024-12-10", "products": [ "Teams" ],