diff --git a/.github/workflows/build-complete-samples.yml b/.github/workflows/build-complete-samples.yml index 9e357bec34..8e873f7ea9 100644 --- a/.github/workflows/build-complete-samples.yml +++ b/.github/workflows/build-complete-samples.yml @@ -490,7 +490,7 @@ jobs: - project_path: 'samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.csproj' name: 'msgext-thirdparty-storage' - version: '6.0.x' + version: '10.0.x' - project_path: 'samples/bot-auth0-adaptivecard/csharp/TeamsAuth0Bot.csproj' name: 'bot-auth0-adaptivecard' diff --git a/samples/bot-teams-authentication/csharp/AdapterWithErrorHandler.cs b/samples/bot-teams-authentication/csharp/AdapterWithErrorHandler.cs deleted file mode 100644 index af3d60e972..0000000000 --- a/samples/bot-teams-authentication/csharp/AdapterWithErrorHandler.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Net.Http; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Builder.Teams; -using Microsoft.Bot.Builder.TraceExtensions; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; - -namespace Microsoft.BotBuilderSamples -{ - /// - /// A CloudAdapter with error handling capabilities. - /// - public class AdapterWithErrorHandler : CloudAdapter - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The HTTP client factory. - /// The logger. - /// The storage. - /// The conversation state. - public AdapterWithErrorHandler(IConfiguration configuration, IHttpClientFactory httpClientFactory, ILogger logger, IStorage storage, ConversationState conversationState) - : base(configuration, httpClientFactory, logger) - { - if (configuration.GetValue("UseSingleSignOn")) - { - Use(new TeamsSSOTokenExchangeMiddleware(storage, configuration["ConnectionName"])); - } - - 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 the line below for local debugging. - // await turnContext.SendActivityAsync($"Sorry, it looks like something went wrong. Exception Caught: {exception.Message}"); - - if (conversationState != null) - { - try - { - // Delete the conversationState for the current conversation to prevent the - // bot from getting stuck in an error-loop caused by being in a bad state. - // ConversationState should be thought of as similar to "cookie-state" in a Web page. - await conversationState.DeleteAsync(turnContext); - } - catch (Exception e) - { - logger.LogError(e, $"Exception caught on attempting to delete ConversationState : {e.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-teams-authentication/csharp/Bots/DialogBot.cs b/samples/bot-teams-authentication/csharp/Bots/DialogBot.cs deleted file mode 100644 index f4cecd07b1..0000000000 --- a/samples/bot-teams-authentication/csharp/Bots/DialogBot.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Dialogs; -using Microsoft.Bot.Builder.Teams; -using Microsoft.Bot.Schema; -using Microsoft.Extensions.Logging; - -namespace Microsoft.BotBuilderSamples -{ - /// - /// This IBot implementation can run any type of Dialog. The use of type parameterization allows multiple different bots - /// to be run at different endpoints within the same project. This can be achieved by defining distinct Controller types - /// each with dependency on distinct IBot types, this way ASP Dependency Injection can glue everything together without ambiguity. - /// The ConversationState is used by the Dialog system. The UserState isn't, however, it might have been used in a Dialog implementation, - /// and the requirement is that all BotState objects are saved at the end of a turn. - /// - /// The type of Dialog to be run. - public class DialogBot : TeamsActivityHandler where T : Dialog - { - protected readonly BotState _conversationState; - protected readonly Dialog _dialog; - protected readonly ILogger _logger; - protected readonly BotState _userState; - - /// - /// Initializes a new instance of the class. - /// - /// The conversation state. - /// The user state. - /// The dialog to be run. - /// The logger. - public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger> logger) - { - _conversationState = conversationState; - _userState = userState; - _dialog = dialog; - _logger = logger; - } - - /// - /// Called at the end of each turn. Saves any state changes that might have occurred during the turn. - /// - /// The context object for this turn. - /// A cancellation token. - /// A task that represents the work queued to execute. - public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) - { - await base.OnTurnAsync(turnContext, cancellationToken); - - // Save any state changes that might have occurred during the turn. - await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); - await _userState.SaveChangesAsync(turnContext, false, cancellationToken); - } - - /// - /// Handles the message activity. - /// - /// The context object for this turn. - /// A cancellation token. - /// A task that represents the work queued to execute. - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - _logger.LogInformation("Running dialog with Message Activity."); - - // Run the Dialog with the new message Activity. - await _dialog.RunAsync(turnContext, _conversationState.CreateProperty(nameof(DialogState)), cancellationToken); - } - } -} diff --git a/samples/bot-teams-authentication/csharp/Bots/TeamsBot.cs b/samples/bot-teams-authentication/csharp/Bots/TeamsBot.cs deleted file mode 100644 index 3d11020e45..0000000000 --- a/samples/bot-teams-authentication/csharp/Bots/TeamsBot.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Dialogs; -using Microsoft.Bot.Schema; -using Microsoft.Extensions.Logging; - -namespace Microsoft.BotBuilderSamples -{ - /// - /// This bot is derived from the class and handles Teams-specific activities. - /// - /// The type of Dialog to be run. - public class TeamsBot : DialogBot where T : Dialog - { - /// - /// Initializes a new instance of the class. - /// - /// The conversation state. - /// The user state. - /// The dialog to be run. - /// The logger. - public TeamsBot(ConversationState conversationState, UserState userState, T dialog, ILogger> logger) - : base(conversationState, userState, dialog, logger) - { - } - - /// - /// Handles the event when members are added to the conversation. - /// - /// The list of members added to the conversation. - /// The context object for this turn. - /// A cancellation token. - /// A task that represents the work queued to execute. - protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) - { - foreach (var member in turnContext.Activity.MembersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to AuthenticationBot. Type anything to get logged in. Type 'logout' to sign-out."), cancellationToken); - } - } - } - - /// - /// Handles the Teams Sign-in verification state. - /// - /// The context object for this turn. - /// A cancellation token. - /// A task that represents the work queued to execute. - protected override async Task OnTeamsSigninVerifyStateAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - _logger.LogInformation("Running dialog with signin/verifystate from an Invoke Activity."); - - // The OAuth Prompt needs to see the Invoke Activity in order to complete the login process. - - // Run the Dialog with the new Invoke Activity. - await _dialog.RunAsync(turnContext, _conversationState.CreateProperty(nameof(DialogState)), cancellationToken); - } - } -} diff --git a/samples/bot-teams-authentication/csharp/Controllers/BotController.cs b/samples/bot-teams-authentication/csharp/Controllers/BotController.cs deleted file mode 100644 index fa4ea81324..0000000000 --- a/samples/bot-teams-authentication/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 -{ - // 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-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json deleted file mode 100644 index c2c03ef307..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "azureBotId": { - "value": "" - }, - "azureBotSku": { - "value": "S1" - }, - "azureBotRegion": { - "value": "global" - }, - "botEndpoint": { - "value": "" - }, - "appType": { - "value": "MultiTenant" - }, - "appId": { - "value": "" - }, - "UMSIName": { - "value": "" - }, - "UMSIResourceGroupName": { - "value": "" - }, - "tenantId": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json deleted file mode 100644 index c4b2909008..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appServiceName": { - "value": "" - }, - "existingAppServicePlanName": { - "value": "" - }, - "existingAppServicePlanLocation": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appType": { - "value": "MultiTenant" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "UMSIName": { - "value": "" - }, - "UMSIResourceGroupName": { - "value": "" - }, - "tenantId": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/readme.md b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/readme.md deleted file mode 100644 index 628f0a9546..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/readme.md +++ /dev/null @@ -1,48 +0,0 @@ -# Usage -The BotApp must be deployed prior to AzureBot. - -Command line: -- az login -- az deployment group create --resource-group --template-file --parameters @ - -# parameters-for-template-BotApp-with-rg: - -- **appServiceName**:(required) The Name of the Bot App Service. - -- (choose an existingAppServicePlan or create a new AppServicePlan) - - **existingAppServicePlanName**: The name of the App Service Plan. - - **existingAppServicePlanLocation**: The location of the App Service Plan. - - **newAppServicePlanName**: The name of the App Service Plan. - - **newAppServicePlanLocation**: The location of the App Service Plan. - - **newAppServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. - -- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** - -- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. - -- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. - -- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. - -- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. - -- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . - -MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource - - - -# parameters-for-template-AzureBot-with-rg: - -- **azureBotId**:(required) The globally unique and immutable bot ID. -- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. - -- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** -- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. -- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. -- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . - -MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/template-AzureBot-with-rg.json b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/template-AzureBot-with-rg.json deleted file mode 100644 index a8a960066f..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/template-AzureBot-with-rg.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "azureBotId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID." - } - }, - "azureBotSku": { - "type": "string", - "defaultValue": "S1", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Allowed values are: F0, S1(default)." - } - }, - "azureBotRegion": { - "type": "string", - "defaultValue": "global", - "metadata": { - "description": "Specifies the location of the new AzureBot. Allowed values are: global(default), westeurope." - } - }, - "botEndpoint": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." - } - }, - "appType": { - "type": "string", - "defaultValue": "MultiTenant", - "allowedValues": [ - "MultiTenant", - "SingleTenant", - "UserAssignedMSI" - ], - "metadata": { - "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "UMSIName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication." - } - }, - "UMSIResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication." - } - }, - "tenantId": { - "type": "string", - "defaultValue": "[subscription().tenantId]", - "metadata": { - "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." - } - } - }, - "variables": { - "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", - "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", - "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", - "appTypeDef": { - "MultiTenant": { - "tenantId": "", - "msiResourceId": "" - }, - "SingleTenant": { - "tenantId": "[variables('tenantId')]", - "msiResourceId": "" - }, - "UserAssignedMSI": { - "tenantId": "[variables('tenantId')]", - "msiResourceId": "[variables('msiResourceId')]" - } - }, - "appType": { - "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", - "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]" - } - }, - "resources": [ - { - "apiVersion": "2021-05-01-preview", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('azureBotId')]", - "location": "[parameters('azureBotRegion')]", - "kind": "azurebot", - "sku": { - "name": "[parameters('azureBotSku')]" - }, - "properties": { - "displayName": "[parameters('azureBotId')]", - "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "msaAppTenantId": "[variables('appType').tenantId]", - "msaAppMSIResourceId": "[variables('appType').msiResourceId]", - "msaAppType": "[parameters('appType')]", - "luisAppIds": [], - "schemaTransformationVersion": "1.3", - "isCmekEnabled": false, - "isIsolated": false - }, - "dependsOn": [] - } - ] -} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/template-BotApp-with-rg.json b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/template-BotApp-with-rg.json deleted file mode 100644 index ce3bb6322a..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployUseExistResourceGroup/template-BotApp-with-rg.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appServiceName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App." - } - }, - "existingAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "existingAppServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appType": { - "type": "string", - "defaultValue": "MultiTenant", - "allowedValues": [ - "MultiTenant", - "SingleTenant", - "UserAssignedMSI" - ], - "metadata": { - "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." - } - }, - "UMSIName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." - } - }, - "UMSIResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." - } - }, - "tenantId": { - "type": "string", - "defaultValue": "[subscription().tenantId]", - "metadata": { - "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." - } - } - }, - "variables": { - "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", - "useExistingServicePlan": "[not(empty(parameters('existingAppServicePlanName')))]", - "servicePlanName": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanName'), parameters('newAppServicePlanName'))]", - "servicePlanLocation": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanLocation'), parameters('newAppServicePlanLocation'))]", - "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", - "appTypeDef": { - "MultiTenant": { - "tenantId": "", - "identity": { "type": "None" } - }, - "SingleTenant": { - "tenantId": "[variables('tenantId')]", - "identity": { "type": "None" } - }, - "UserAssignedMSI": { - "tenantId": "[variables('tenantId')]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[variables('msiResourceId')]": {} - } - } - } - }, - "appType": { - "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", - "identity": "[variables('appTypeDef')[parameters('appType')].identity]" - } - }, - "resources": [ - { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[parameters('newAppServicePlanLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "properties": { - "name": "[variables('servicePlanName')]" - } - }, - { - "comments": "Create a Web App using an App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "location": "[variables('servicePlanLocation')]", - "kind": "app", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[parameters('appServiceName')]", - "identity": "[variables('appType').identity]", - "properties": { - "name": "[parameters('appServiceName')]", - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "siteConfig": { - "appSettings": [ - { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "10.14.1" - }, - { - "name": "MicrosoftAppType", - "value": "[parameters('appType')]" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - }, - { - "name": "MicrosoftAppTenantId", - "value": "[variables('appType').tenantId]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - }, - "webSocketsEnabled": true - } - } - } - ] -} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json deleted file mode 100644 index 44f169e4d5..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupName": { - "value": "" - }, - "groupLocation": { - "value": "" - }, - "azureBotId": { - "value": "" - }, - "azureBotSku": { - "value": "S1" - }, - "azureBotRegion": { - "value": "global" - }, - "botEndpoint": { - "value": "" - }, - "appType": { - "value": "MultiTenant" - }, - "appId": { - "value": "" - }, - "UMSIName": { - "value": "" - }, - "UMSIResourceGroupName": { - "value": "" - }, - "tenantId": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json deleted file mode 100644 index 8abb03d597..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupName": { - "value": "" - }, - "groupLocation": { - "value": "" - }, - "appServiceName": { - "value": "" - }, - "appServicePlanName": { - "value": "" - }, - "appServicePlanLocation": { - "value": "" - }, - "appServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appType": { - "value": "MultiTenant" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "UMSIName": { - "value": "" - }, - "UMSIResourceGroupName": { - "value": "" - }, - "tenantId": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/readme.md b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/readme.md deleted file mode 100644 index 23bf7a5a51..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/readme.md +++ /dev/null @@ -1,45 +0,0 @@ -# Usage -The BotApp must be deployed prior to AzureBot. - -Command line: -- az login -- az deployment sub create --template-file --location --parameters @ - -# parameters-for-template-BotApp-new-rg: - -- **groupName**:(required) Specifies the name of the new Resource Group. -- **groupLocation**:(required) Specifies the location of the new Resource Group. - -- **appServiceName**:(required) The location of the App Service Plan. -- **appServicePlanName**:(required) The name of the App Service Plan. -- **appServicePlanLocation**: The location of the App Service Plan. Defaults to use groupLocation. -- **appServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. - -- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** -- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. -- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. -- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. -- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . - -MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource - - - -# parameters-for-template-AzureBot-new-rg: - -- **groupName**:(required) Specifies the name of the new Resource Group. -- **groupLocation**:(required) Specifies the location of the new Resource Group. - -- **azureBotId**:(required) The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable. -- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. - -- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** -- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. -- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. -- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . - -MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/template-AzureBot-new-rg.json b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/template-AzureBot-new-rg.json deleted file mode 100644 index ae073b7939..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/template-AzureBot-new-rg.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "groupLocation": { - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "azureBotId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID." - } - }, - "azureBotSku": { - "type": "string", - "defaultValue": "S1", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "azureBotRegion": { - "type": "string", - "defaultValue": "global", - "metadata": { - "description": "" - } - }, - "botEndpoint": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." - } - }, - "appType": { - "type": "string", - "defaultValue": "MultiTenant", - "allowedValues": [ - "MultiTenant", - "SingleTenant", - "UserAssignedMSI" - ], - "metadata": { - "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "tenantId": { - "type": "string", - "defaultValue": "[subscription().tenantId]", - "metadata": { - "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." - } - }, - "UMSIName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication." - } - }, - "UMSIResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication." - } - } - }, - "variables": { - "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", - "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", - "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", - "appTypeDef": { - "MultiTenant": { - "tenantId": "", - "msiResourceId": "" - }, - "SingleTenant": { - "tenantId": "[variables('tenantId')]", - "msiResourceId": "" - }, - "UserAssignedMSI": { - "tenantId": "[variables('tenantId')]", - "msiResourceId": "[variables('msiResourceId')]" - } - }, - "appType": { - "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", - "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]" - } - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "apiVersion": "2021-03-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('azureBotId')]", - "location": "[parameters('azureBotRegion')]", - "kind": "azurebot", - "sku": { - "name": "[parameters('azureBotSku')]" - }, - "properties": { - "name": "[parameters('azureBotId')]", - "displayName": "[parameters('azureBotId')]", - "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "msaAppTenantId": "[variables('appType').tenantId]", - "msaAppMSIResourceId": "[variables('appType').msiResourceId]", - "msaAppType": "[parameters('appType')]", - "luisAppIds": [], - "schemaTransformationVersion": "1.3", - "isCmekEnabled": false, - "isIsolated": false - } - } - ] - } - } - } - ] -} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/template-BotApp-new-rg.json b/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/template-BotApp-new-rg.json deleted file mode 100644 index 560bbbc443..0000000000 --- a/samples/bot-teams-authentication/csharp/DeploymentTemplates/DeployWithNewResourceGroup/template-BotApp-new-rg.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "groupLocation": { - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "appServiceName": { - "type": "string", - "metadata": { - "description": "The globally unique name of the Web App." - } - }, - "appServicePlanName": { - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "appServicePlanLocation": { - "type": "string", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "appServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "tenantId": { - "type": "string", - "defaultValue": "[subscription().tenantId]", - "metadata": { - "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." - } - }, - "appType": { - "type": "string", - "defaultValue": "MultiTenant", - "allowedValues": [ - "MultiTenant", - "SingleTenant", - "UserAssignedMSI" - ], - "metadata": { - "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types." - } - }, - "UMSIName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication." - } - }, - "UMSIResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication." - } - } - }, - "variables": { - "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", - "appServicePlanName": "[parameters('appServicePlanName')]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), parameters('groupLocation'), parameters('appServicePlanLocation'))]", - "appServiceName": "[parameters('appServiceName')]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]", - "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", - "appTypeDef": { - "MultiTenant": { - "tenantId": "", - "identity": { "type": "None" } - }, - "SingleTenant": { - "tenantId": "[variables('tenantId')]", - "identity": { "type": "None" } - }, - "UserAssignedMSI": { - "tenantId": "[variables('tenantId')]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[variables('msiResourceId')]": {} - } - } - } - }, - "appType": { - "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", - "identity": "[variables('appTypeDef')[parameters('appType')].identity]" - } - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new App Service Plan", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('appServicePlanSku')]", - "properties": { - "name": "[variables('appServicePlanName')]" - } - }, - { - "comments": "Create a Web App using the new App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "location": "[variables('resourcesLocation')]", - "kind": "app", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('appServiceName')]", - "identity": "[variables('appType').identity]", - "properties": { - "name": "[variables('appServiceName')]", - "serverFarmId": "[variables('appServicePlanName')]", - "siteConfig": { - "appSettings": [ - { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "10.14.1" - }, - { - "name": "MicrosoftAppType", - "value": "[parameters('appType')]" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - }, - { - "name": "MicrosoftAppTenantId", - "value": "[variables('appType').tenantId]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - }, - "webSocketsEnabled": true - } - } - } - ], - "outputs": {} - } - } - } - ] -} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/Dialogs/LogoutDialog.cs b/samples/bot-teams-authentication/csharp/Dialogs/LogoutDialog.cs deleted file mode 100644 index 2d0253ff8b..0000000000 --- a/samples/bot-teams-authentication/csharp/Dialogs/LogoutDialog.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Dialogs; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Bot.Schema; - -namespace Microsoft.BotBuilderSamples -{ - /// - /// A dialog that handles user logout. - /// - public class LogoutDialog : ComponentDialog - { - /// - /// Initializes a new instance of the class. - /// - /// The dialog ID. - /// The connection name for the OAuth provider. - public LogoutDialog(string id, string connectionName) - : base(id) - { - ConnectionName = connectionName; - } - - /// - /// Gets the connection name for the OAuth provider. - /// - protected string ConnectionName { get; } - - /// - /// Called when the dialog is started and pushed onto the dialog stack. - /// - /// The dialog context for the current turn of conversation. - /// Optional, initial information to pass to the dialog. - /// A cancellation token. - /// A task that represents the work queued to execute. - protected override async Task OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default) - { - var result = await InterruptAsync(innerDc, cancellationToken); - if (result != null) - { - return result; - } - - return await base.OnBeginDialogAsync(innerDc, options, cancellationToken); - } - - /// - /// Called when the dialog is continued, where it is the active dialog and the user replies with a new activity. - /// - /// The dialog context for the current turn of conversation. - /// A cancellation token. - /// A task that represents the work queued to execute. - protected override async Task OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default) - { - var result = await InterruptAsync(innerDc, cancellationToken); - if (result != null) - { - return result; - } - - return await base.OnContinueDialogAsync(innerDc, cancellationToken); - } - - /// - /// Checks for the "logout" command and signs the user out if found. - /// - /// The dialog context for the current turn of conversation. - /// A cancellation token. - /// A task that represents the work queued to execute. - private async Task InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default) - { - if (innerDc.Context.Activity.Type == ActivityTypes.Message) - { - var text = innerDc.Context.Activity.Text.ToLowerInvariant(); - - // Allow logout anywhere in the command - if (text.Contains("logout")) - { - // The UserTokenClient encapsulates the authentication processes. - var userTokenClient = innerDc.Context.TurnState.Get(); - await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false); - - await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); - return await innerDc.CancelAllDialogsAsync(cancellationToken); - } - } - - return null; - } - } -} diff --git a/samples/bot-teams-authentication/csharp/Dialogs/MainDialog.cs b/samples/bot-teams-authentication/csharp/Dialogs/MainDialog.cs deleted file mode 100644 index 1e2c46db43..0000000000 --- a/samples/bot-teams-authentication/csharp/Dialogs/MainDialog.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Dialogs; -using Microsoft.Bot.Schema; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; - -namespace Microsoft.BotBuilderSamples -{ - /// - /// Main dialog for handling user authentication and displaying token information. - /// - public class MainDialog : LogoutDialog - { - protected readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The logger. - public MainDialog(IConfiguration configuration, ILogger logger) - : base(nameof(MainDialog), configuration["ConnectionName"]) - { - _logger = logger; - - AddDialog(new OAuthPrompt( - nameof(OAuthPrompt), - new OAuthPromptSettings - { - ConnectionName = ConnectionName, - Text = "Please Sign In", - Title = "Sign In", - Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5) - EndOnInvalidMessage = true - })); - - AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); - - AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] - { - PromptStepAsync, - LoginStepAsync, - DisplayTokenPhase1Async, - DisplayTokenPhase2Async, - })); - - // The initial child Dialog to run. - InitialDialogId = nameof(WaterfallDialog); - } - - /// - /// Prompts the user to sign in. - /// - /// The waterfall step context. - /// A cancellation token. - /// A task that represents the work queued to execute. - private async Task PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) - { - return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); - } - - /// - /// Handles the login step and retrieves the user's token. - /// - /// The waterfall step context. - /// A cancellation token. - /// A task that represents the work queued to execute. - private async Task LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) - { - var tokenResponse = (TokenResponse)stepContext.Result; - if (tokenResponse?.Token != null) - { - var client = new SimpleGraphClient(tokenResponse.Token); - var me = await client.GetMeAsync(); - var title = !string.IsNullOrEmpty(me.JobTitle) ? me.JobTitle : "Unknown"; - - await stepContext.Context.SendActivityAsync($"You're logged in as {me.DisplayName} ({me.UserPrincipalName}); your job title is: {title}"); - - return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Would you like to view your token?") }, cancellationToken); - } - - await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful, please try again."), cancellationToken); - return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); - } - - /// - /// Displays a thank you message and prompts the user to view their token. - /// - /// The waterfall step context. - /// A cancellation token. - /// A task that represents the work queued to execute. - private async Task DisplayTokenPhase1Async(WaterfallStepContext stepContext, CancellationToken cancellationToken) - { - await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank you."), cancellationToken); - - var result = (bool)stepContext.Result; - if (result) - { - return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), cancellationToken: cancellationToken); - } - - return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); - } - - /// - /// Displays the user's token. - /// - /// The waterfall step context. - /// A cancellation token. - /// A task that represents the work queued to execute. - private async Task DisplayTokenPhase2Async(WaterfallStepContext stepContext, CancellationToken cancellationToken) - { - var tokenResponse = (TokenResponse)stepContext.Result; - if (tokenResponse != null) - { - await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Here is your token: {tokenResponse.Token}"), cancellationToken); - } - - return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); - } - } -} diff --git a/samples/bot-teams-authentication/csharp/M365Agent/.gitignore b/samples/bot-teams-authentication/csharp/M365Agent/.gitignore new file mode 100644 index 0000000000..c5cae9258c --- /dev/null +++ b/samples/bot-teams-authentication/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/meetings-sidepanel/csharp/M365Agent/M365Agent.ttkproj b/samples/bot-teams-authentication/csharp/M365Agent/M365Agent.atkproj similarity index 61% rename from samples/meetings-sidepanel/csharp/M365Agent/M365Agent.ttkproj rename to samples/bot-teams-authentication/csharp/M365Agent/M365Agent.atkproj index 22004a0b22..2e33a4b15b 100644 --- a/samples/meetings-sidepanel/csharp/M365Agent/M365Agent.ttkproj +++ b/samples/bot-teams-authentication/csharp/M365Agent/M365Agent.atkproj @@ -1,12 +1,10 @@ - + - f3cd3e47-e68e-4923-93e3-44df8a1afa6a + b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf - - diff --git a/samples/bot-teams-authentication/csharp/M365Agent/README.md b/samples/bot-teams-authentication/csharp/M365Agent/README.md new file mode 100644 index 0000000000..732169accf --- /dev/null +++ b/samples/bot-teams-authentication/csharp/M365Agent/README.md @@ -0,0 +1,21 @@ +# Welcome to Microsoft 365 Agents Toolkit! + +## Quick Start +1. Press F5, or select Debug > Start Debugging menu in Visual Studio to start your app + +2. In Microsoft 365 Agents Playground, type and send anything to your bot to get a response + + +## Run the app on other platforms + +The Teams app can run in other platforms like Outlook and Microsoft 365 app. See https://aka.ms/vs-ttk-debug-multi-profiles for more details. + +## Get more info + +New to Teams app development or Microsoft 365 Agents Toolkit? Explore Teams app manifests, cloud deployment, and much more in the https://aka.ms/teams-toolkit-vs-docs. + +## Report an issue + +Select Visual Studio > Help > Send Feedback > Report a Problem. +Or, create an issue directly in our GitHub repository: +https://github.com/OfficeDev/TeamsFx/issues diff --git a/samples/bot-teams-authentication/csharp/M365Agent/aad.manifest.json b/samples/bot-teams-authentication/csharp/M365Agent/aad.manifest.json index bd13dd6e94..edfbfd7404 100644 --- a/samples/bot-teams-authentication/csharp/M365Agent/aad.manifest.json +++ b/samples/bot-teams-authentication/csharp/M365Agent/aad.manifest.json @@ -1,107 +1,114 @@ { - "id": "${{AAD_APP_OBJECT_ID}}", - "appId": "${{AAD_APP_CLIENT_ID}}", - "name": "bot-teams-authentication-aad", - "accessTokenAcceptedVersion": 2, - "signInAudience": "AzureADMultipleOrgs", - "oauth2AllowIdTokenImplicitFlow": true, - "oauth2AllowImplicitFlow": true, - "optionalClaims": { - "idToken": [], - "accessToken": [ - { - "name": "idtyp", - "source": null, - "essential": false, - "additionalProperties": [] - } - ], - "saml2Token": [] - }, - "requiredResourceAccess": [ - { - "resourceAppId": "Microsoft Graph", - "resourceAccess": [ - { - "id": "User.Read", - "type": "Scope" - } - ] - } - ], - "oauth2Permissions": [ - { - "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" - } + "id": "${{BOT_OBJECT_ID}}", + "appId": "${{BOT_ID}}", + "displayName": "bot-teams-authentication-aad", + "identifierUris": [ + "api://botid-${{BOT_ID}}" + ], + "signInAudience": "AzureADMultipleOrgs", + "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", - "permissionIds": [ - "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" - ] - }, - { - "appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346", - "permissionIds": [ - "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" - ] - }, - { - "appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c", - "permissionIds": [ - "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" - ] - }, - { - "appId": "00000002-0000-0ff1-ce00-000000000000", - "permissionIds": [ - "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" - ] - }, - { - "appId": "bc59ab01-8403-45c6-8796-ac3ef710b3e3", - "permissionIds": [ - "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" - ] - }, - { - "appId": "0ec893e0-5785-4de6-99da-4ed124e5296c", - "permissionIds": [ - "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" - ] - }, - { - "appId": "4765445b-32c6-49b0-83e6-1d93765276ca", - "permissionIds": [ - "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" - ] - }, - { - "appId": "4345a7b9-9a63-4910-a426-35363201d503", - "permissionIds": [ - "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" - ] - } - ], - "identifierUris":[ - "api://botid-${{AAD_APP_CLIENT_ID}}" - ], - "replyUrlsWithType":[ { - "url": "https://${{BOT_DOMAIN}}/auth-end.html", - "type": "Web" + "appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264", + "delegatedPermissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] }, { - "url": "https://token.botframework.com/.auth/web/redirect", - "type": "Web" - } + "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": { + "redirectUris": [] + }, + "requiredResourceAccess": [ + { + "resourceAppId": "Microsoft Graph", + "resourceAccess": [ + { + "id": "User.Read", + "type": "Scope" + } + ] + } + ], + "web": { + "redirectUris": [ + "https://${{BOT_DOMAIN}}/auth-end.html", + "https://token.botframework.com/.auth/web/redirect" + ], + "implicitGrantSettings": { + "enableIdTokenIssuance": true, + "enableAccessTokenIssuance": true + } + }, + "spa": { + "redirectUris": [] + } } diff --git a/samples/bot-teams-authentication/csharp/M365Agent/appPackage/color.png b/samples/bot-teams-authentication/csharp/M365Agent/appPackage/color.png index b8cf81afbe..01aa37e347 100644 Binary files a/samples/bot-teams-authentication/csharp/M365Agent/appPackage/color.png and b/samples/bot-teams-authentication/csharp/M365Agent/appPackage/color.png differ diff --git a/samples/bot-teams-authentication/csharp/M365Agent/appPackage/manifest.json b/samples/bot-teams-authentication/csharp/M365Agent/appPackage/manifest.json index 94a84736ef..4e1c1c57f9 100644 --- a/samples/bot-teams-authentication/csharp/M365Agent/appPackage/manifest.json +++ b/samples/bot-teams-authentication/csharp/M365Agent/appPackage/manifest.json @@ -1,17 +1,17 @@ { - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json", - "manifestVersion": "1.19", + "$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": "Microsoft", - "websiteUrl": "https://example.azurewebsites.net", - "privacyUrl": "https://example.azurewebsites.net/privacy", - "termsOfUseUrl": "https://example.azurewebsites.net/termsofuse" + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termofuse" }, "icons": { - "outline": "outline.png", - "color": "color.png" + "color": "color.png", + "outline": "outline.png" }, "name": { "short": "Teams SSO Auth", @@ -24,16 +24,34 @@ "accentColor": "#FFFFFF", "bots": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "${{BOT_ID}}", "scopes": [ - "personal" + "personal", + "groupChat" ], + "supportsFiles": false, "isNotificationOnly": false, - "supportsCalling": false, - "supportsVideo": false, - "supportsFiles": false + "commandLists": [ + { + "scopes": [ "personal", "team", "groupChat" ], + "commands": [ + { + "title": "/signin", + "description": "Sign in to the app" + }, + { + "title": "/signout", + "description": "Sign out from the app" + } + ] + } + ] } ], + "composeExtensions": [ + ], + "configurableTabs": [], + "staticTabs": [], "permissions": [ "identity", "messageTeamMembers" @@ -43,7 +61,7 @@ "${{BOT_DOMAIN}}" ], "webApplicationInfo": { - "id": "${{AAD_APP_CLIENT_ID}}", - "resource": "api://botid-${{AAD_APP_CLIENT_ID}}" + "id": "${{BOT_ID}}", + "resource": "api://botid-${{BOT_ID}}" } } \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/M365Agent/appPackage/outline.png b/samples/bot-teams-authentication/csharp/M365Agent/appPackage/outline.png index 2c3bf6fa65..f7a4c86447 100644 Binary files a/samples/bot-teams-authentication/csharp/M365Agent/appPackage/outline.png and b/samples/bot-teams-authentication/csharp/M365Agent/appPackage/outline.png differ diff --git a/samples/bot-teams-authentication/csharp/M365Agent/env/.env.dev b/samples/bot-teams-authentication/csharp/M365Agent/env/.env.dev new file mode 100644 index 0000000000..df4f9da508 --- /dev/null +++ b/samples/bot-teams-authentication/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-teams-authentication/csharp/M365Agent/env/.env.local b/samples/bot-teams-authentication/csharp/M365Agent/env/.env.local index ed425de596..27e16b8f5b 100644 --- a/samples/bot-teams-authentication/csharp/M365Agent/env/.env.local +++ b/samples/bot-teams-authentication/csharp/M365Agent/env/.env.local @@ -7,21 +7,17 @@ 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= +TEAMS_APP_TENANT_ID= +BOT_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= -CONNECTION_NAME= -AAD_APP_ACCESS_AS_USER_PERMISSION_ID= \ No newline at end of file +AAD_APP_OAUTH_AUTHORITY_HOST=https://login.microsoftonline.com +CONNECTION_NAME=oauthbotsetting +AAD_APP_ACCESS_AS_USER_PERMISSION_ID= +TEAMSFX_M365_USER_NAME= + +BOT_ENDPOINT= +BOT_DOMAIN= \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.bicep b/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.bicep index 145ce9cd4f..658e412a21 100644 --- a/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.bicep +++ b/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.bicep @@ -3,74 +3,84 @@ @description('Used to generate names for all resources in this file') param resourceBaseName string +param webAppSKU string + @maxLength(42) param botDisplayName string -param botServiceName string = resourceBaseName -param botServiceSku string = 'F0' -param botAadAppClientId string -param botAppDomain string +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param identityName string = resourceBaseName +param location string = resourceGroup().location -param aadAppClientId string -@secure() -param aadAppClientSecret string -param microsoftAppType string -param microsoftAppTenantId string +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + location: location + name: identityName +} -// 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 : '' - } +// 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}': {} + } } } -resource botServiceConnection 'Microsoft.BotService/botServices/connections@2021-03-01' = { - parent: botService - name: 'oauthbotsetting' - location: 'global' - properties: { - serviceProviderDisplayName: 'Azure Active Directory v2' - serviceProviderId: '30dd229c-58e3-4a48-bdfd-91ec48eb906c' - scopes: 'User.Read' - parameters: [ - { - key: 'clientId' - value: aadAppClientId - } - { - key: 'clientSecret' - value: aadAppClientSecret - } - { - key: 'tenantID' - value: microsoftAppTenantId - } - { - key: 'tokenExchangeUrl' - value: 'api://botid-${aadAppClientId}' - } - ] +// 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 } } -output CONNECTION_NAME string = botServiceConnection.name +// 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-teams-authentication/csharp/M365Agent/infra/azure.parameters.json b/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.parameters.json index 8d9f142ad1..9f078bcf6d 100644 --- a/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.parameters.json +++ b/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.parameters.json @@ -1,30 +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": "bot-teams-authentication" - }, - "aadAppClientId": { - "value": "${{AAD_APP_CLIENT_ID}}" - }, - "aadAppClientSecret": { - "value": "${{SECRET_AAD_APP_CLIENT_SECRET}}" - }, - "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": "TeamsAuth" + } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.parameters.local.json b/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.parameters.local.json new file mode 100644 index 0000000000..bde37d9b89 --- /dev/null +++ b/samples/bot-teams-authentication/csharp/M365Agent/infra/azure.parameters.local.json @@ -0,0 +1,30 @@ +{ + "$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": "${{BOT_ID}}" + }, + "botAppDomain": { + "value": "${{BOT_DOMAIN}}" + }, + "botDisplayName": { + "value": "bot-teams-authentication" + }, + "aadAppClientId": { + "value": "${{BOT_ID}}" + }, + "aadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + "microsoftAppType": { + "value": "SingleTenant" + }, + "microsoftAppTenantId": { + "value": "${{AAD_APP_TENANT_ID}}" + } + } +} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/M365Agent/infra/botRegistration/azurebot.bicep b/samples/bot-teams-authentication/csharp/M365Agent/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..a5a27b8fe4 --- /dev/null +++ b/samples/bot-teams-authentication/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-teams-authentication/csharp/M365Agent/infra/botRegistration/azurebotlocal.bicep b/samples/bot-teams-authentication/csharp/M365Agent/infra/botRegistration/azurebotlocal.bicep new file mode 100644 index 0000000000..3a5b02fb6e --- /dev/null +++ b/samples/bot-teams-authentication/csharp/M365Agent/infra/botRegistration/azurebotlocal.bicep @@ -0,0 +1,76 @@ +@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 botAadAppClientId string +param botAppDomain string + +param aadAppClientId string +@secure() +param aadAppClientSecret string +param microsoftAppType string +param microsoftAppTenantId 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: botAadAppClientId + msaAppType: microsoftAppType + msaAppTenantId: microsoftAppType == 'SingleTenant' ? microsoftAppTenantId : '' + } + 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' + } +} + +resource botServiceConnection 'Microsoft.BotService/botServices/connections@2021-03-01' = { + parent: botService + name: 'oauthbotsetting' + location: 'global' + properties: { + serviceProviderDisplayName: 'Azure Active Directory v2' + serviceProviderId: '30dd229c-58e3-4a48-bdfd-91ec48eb906c' + scopes: 'User.Read' + parameters: [ + { + key: 'clientId' + value: aadAppClientId + } + { + key: 'clientSecret' + value: aadAppClientSecret + } + { + key: 'tenantID' + value: microsoftAppTenantId + } + { + key: 'tokenExchangeUrl' + value: 'api://botid-${aadAppClientId}' + } + ] + } +} + +output CONNECTION_NAME string = botServiceConnection.name \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/M365Agent/infra/botRegistration/readme.md b/samples/bot-teams-authentication/csharp/M365Agent/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/bot-teams-authentication/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-teams-authentication/csharp/M365Agent/launchSettings.json b/samples/bot-teams-authentication/csharp/M365Agent/launchSettings.json index d6491ef52c..2af8ce7a8a 100644 --- a/samples/bot-teams-authentication/csharp/M365Agent/launchSettings.json +++ b/samples/bot-teams-authentication/csharp/M365Agent/launchSettings.json @@ -1,15 +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/bot-teams-authentication/csharp/M365Agent/m365agents.local.yml b/samples/bot-teams-authentication/csharp/M365Agent/m365agents.local.yml index e017c50b86..c1e398aa78 100644 --- a/samples/bot-teams-authentication/csharp/M365Agent/m365agents.local.yml +++ b/samples/bot-teams-authentication/csharp/M365Agent/m365agents.local.yml @@ -1,61 +1,59 @@ -# 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-teams-authentication-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-teams-authentication-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-teams-authentication${{APP_NAME_SUFFIX}} + name: TeamsAuth${{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: TeamsAuth${{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 + tenantId: AAD_APP_TENANT_ID + authority: AAD_APP_OAUTH_AUTHORITY + authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST + - uses: arm/deploy # Deploy given ARM templates parallelly. 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 + - path: ./infra/botRegistration/azurebotlocal.bicep + parameters: ./infra/azure.parameters.local.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. - # Generate runtime appsettings to JSON file + # Generate runtime appsettings to JSON file - uses: file/createOrUpdateJsonFile with: - target: ../appsettings.json + target: ../TeamsAuth/appsettings.Development.json content: - MicrosoftAppType: ${{MICROSOFT_APP_TYPE}} - MicrosoftAppId: ${{AAD_APP_CLIENT_ID}} - MicrosoftAppPassword: ${{SECRET_AAD_APP_CLIENT_SECRET}} - ConnectionName: ${{CONNECTION_NAME}} - MicrosoftAppTenantId: ${{MICROSOFT_APP_TENANT_ID}} + Teams: + ClientId: ${{BOT_ID}} + ClientSecret: ${{SECRET_BOT_PASSWORD}} + TenantId: ${{TEAMS_APP_TENANT_ID}} + ConnectionName: ${{CONNECTION_NAME}} - 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: @@ -67,14 +65,13 @@ provision: 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: @@ -87,4 +84,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/bot-teams-authentication/csharp/M365Agent/m365agents.yml b/samples/bot-teams-authentication/csharp/M365Agent/m365agents.yml index 3fb0ed73d9..707c18ca23 100644 --- a/samples/bot-teams-authentication/csharp/M365Agent/m365agents.yml +++ b/samples/bot-teams-authentication/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-teams-authentication-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: TeamsAuth${{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 + TeamsAuth.csproj + workingDirectory: ../TeamsAuth + # 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: ../TeamsAuth +projectId: 9b3b294e-9461-41f0-a64e-b999fa1c46b9 diff --git a/samples/bot-teams-authentication/csharp/Program.cs b/samples/bot-teams-authentication/csharp/Program.cs deleted file mode 100644 index 291b6c3c7b..0000000000 --- a/samples/bot-teams-authentication/csharp/Program.cs +++ /dev/null @@ -1,41 +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 -{ - /// - /// The main entry point for the application. - /// - public class Program - { - /// - /// The main method, which is the entry point of the application. - /// - /// The command-line arguments. - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - /// - /// Creates the host builder for the application. - /// - /// The command-line arguments. - /// An configured for the application. - 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-teams-authentication/csharp/Properties/launchSettings.json b/samples/bot-teams-authentication/csharp/Properties/launchSettings.json deleted file mode 100644 index ff9d8fe153..0000000000 --- a/samples/bot-teams-authentication/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-teams-authentication/csharp/README.md b/samples/bot-teams-authentication/csharp/README.md index 45ef2d76fe..0fd22c7889 100644 --- a/samples/bot-teams-authentication/csharp/README.md +++ b/samples/bot-teams-authentication/csharp/README.md @@ -10,17 +10,14 @@ languages: extensions: contentType: samples createdDate: "07/10/2019 13:38:25 PM" + updatedDate: "17/12/2025 12:00:00 PM" urlFragment: officedev-microsoft-teams-samples-bot-teams-authentication-csharp --- # Teams Auth Bot -Bot Framework v4 bot using Teams authentication - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to get started with authentication in a bot for Microsoft Teams. - The focus of this sample is how to use the Bot Framework support for oauth in your bot. Teams behaves slightly differently than other channels in this regard. Specifically an Invoke Activity is sent to the bot rather than the Event Activity used by other channels. _This Invoke Activity must be forwarded to the dialog if the OAuthPrompt is being used._ This is done by subclassing the ActivityHandler and this sample includes a reusable TeamsActivityHandler. This class is a candidate for future inclusion in the Bot Framework SDK. -The sample uses the bot authentication capabilities in [Azure Bot Service](https://docs.botframework.com), providing features to make it easier to develop a bot that authenticates users to various identity providers such as Microsoft Entra ID, GitHub, Uber, etc. The OAuth token is then used to make basic Microsoft Graph queries. +The sample uses the bot authentication capabilities in [Azure Bot Service](https://learn.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0), providing features to make it easier to develop a bot that authenticates users to various identity providers such as Microsoft Entra ID, GitHub, Uber, etc. The OAuth token is then used to make basic Microsoft Graph queries. > IMPORTANT: The manifest file in this app adds "token.botframework.com" to the list of `validDomains`. This must be included in any bot that uses the Bot Framework OAuth flow. @@ -30,7 +27,7 @@ This sample utilizes an app setting `UseSingleSignOn` to add `TeamsSSOTokenExcha > IMPORTANT: Teams SSO only works in 1-1 chats, and not group contexts. -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use a bot authentication, as well as how to sign in from a bot. In this sample we are assuming the OAuth 2 provider is Azure Active Directory v2 (AADv2) and are utilizing the Microsoft Graph API to retrieve data about the user. Check [here](https://docs.microsoft.com/azure/bot-service/bot-builder-authentication) for information about getting an AADv2 application setup for use in Azure Bot Service. The scopes used in this sample are the following: +This bot has been created using Azure Bot Service, it shows how to use a bot authentication, as well as how to sign in from a bot. In this sample we are assuming the OAuth 2 provider is Azure Active Directory v2 (AADv2) and are utilizing the Microsoft Graph API to retrieve data about the user. Check [here](https://docs.microsoft.com/azure/bot-service/bot-builder-authentication) for information about getting an AADv2 application setup for use in Azure Bot Service. The scopes used in this sample are the following: - `openid` - `User.Read` @@ -104,21 +101,19 @@ Refer to [Bot SSO Setup document](https://github.com/OfficeDev/Microsoft-Teams-S devtunnel host -p 3978 --allow-anonymous ``` -1) Update the `appsettings.json` configuration for the bot to use the MicrosoftAppId, MicrosoftAppPassword, MicrosoftAppTenantId generated in Step 1 (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.) - - Set "MicrosoftAppType" in the `appsettings.json`. (**Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI**) - - Set "ConnectionName" in the `appsettings.json`. The Microsoft Entra ID ConnectionName from the OAuth Connection Settings on Azure Bot registration +1) Update the `appsettings.Development.json` configuration for the bot to use the ClientId, ClientSecret, TenantId generated in Step 1 (App Registration creation). + - Set "BotType" in the `appsettings.Developmet.json`. (**Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI**) + - Set "ConnectionName" in the `appsettings.Development.json`. The Microsoft Entra ID ConnectionName from the OAuth Connection Settings on Azure Bot registration 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 MicrosoftAppId 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 MicrosoftAppId may occur multiple times in the `manifest.json`) - **Edit** the `manifest.json` for `validDomains` 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 `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 scope or 1:1 chat (Supported scope) -**Note**: If you are facing any issue in your app, please uncomment [this](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/bot-teams-authentication/csharp/AdapterWithErrorHandler.cs#L35) line and put your debugger for local debug. - ## Running the sample > Note `manifest.json` contains a `webApplicationInfo` template required for Teams Single Sign On. @@ -141,7 +136,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 Bot Service Documentation](https://learn.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0) - [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-teams-authentication/csharp/SimpleGraphClient.cs b/samples/bot-teams-authentication/csharp/SimpleGraphClient.cs deleted file mode 100644 index db2505a00a..0000000000 --- a/samples/bot-teams-authentication/csharp/SimpleGraphClient.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Graph; -using Microsoft.Graph.Models; -using Microsoft.Kiota.Abstractions.Authentication; -using Microsoft.Graph.Me.SendMail; - -namespace Microsoft.BotBuilderSamples -{ - /// - /// This class is a wrapper for the Microsoft Graph API. - /// See: https://developer.microsoft.com/en-us/graph - /// - public class SimpleGraphClient - { - private readonly string _token; - - /// - /// Initializes a new instance of the class. - /// - /// The OAuth token. - public SimpleGraphClient(string token) - { - if (string.IsNullOrWhiteSpace(token)) - { - throw new ArgumentNullException(nameof(token)); - } - - _token = token; - } - - /// - /// Sends an email on the user's behalf using the Microsoft Graph API. - /// - /// The recipient's email address. - /// The email subject. - /// The email content. - /// A task that represents the work queued to execute. - public async Task SendMailAsync(string toAddress, string subject, string content) - { - if (string.IsNullOrWhiteSpace(toAddress)) - { - throw new ArgumentNullException(nameof(toAddress)); - } - - if (string.IsNullOrWhiteSpace(subject)) - { - throw new ArgumentNullException(nameof(subject)); - } - - if (string.IsNullOrWhiteSpace(content)) - { - throw new ArgumentNullException(nameof(content)); - } - - var graphClient = GetAuthenticatedClient(); - var recipients = new List - { - new Recipient - { - EmailAddress = new EmailAddress - { - Address = toAddress, - }, - }, - }; - - // Create the message. - var email = new Message - { - Body = new ItemBody - { - Content = content, - ContentType = BodyType.Text, - }, - Subject = subject, - ToRecipients = recipients, - }; - - // Send the message. - await graphClient.Me.SendMail.PostAsync(new SendMailPostRequestBody - { - Message = email, - SaveToSentItems = true - }); - } - - /// - /// Gets recent mail for the user using the Microsoft Graph API. - /// - /// An array of recent messages. - public async Task GetRecentMailAsync() - { - var graphClient = GetAuthenticatedClient(); - var messages = await graphClient.Me.MailFolders["Inbox"].Messages.GetAsync(); - return messages.Value.Take(5).ToArray(); - } - - /// - /// Gets information about the user. - /// - /// The user information. - public async Task GetMeAsync() - { - var graphClient = GetAuthenticatedClient(); - var me = await graphClient.Me.GetAsync(); - return me; - } - - /// - /// Gets information about the user's manager. - /// - /// The manager information. - public async Task GetManagerAsync() - { - var graphClient = GetAuthenticatedClient(); - var manager = await graphClient.Me.Manager.GetAsync() as User; - return manager; - } - - // // Gets the user's photo - // public async Task GetPhotoAsync() - // { - // HttpClient client = new HttpClient(); - // client.DefaultRequestHeaders.Add("Authorization", "Bearer " + _token); - // client.DefaultRequestHeaders.Add("Accept", "application/json"); - - // using (var response = await client.GetAsync("https://graph.microsoft.com/v1.0/me/photo/$value")) - // { - // if (!response.IsSuccessStatusCode) - // { - // throw new HttpRequestException($"Graph returned an invalid success code: {response.StatusCode}"); - // } - - // var stream = await response.Content.ReadAsStreamAsync(); - // var bytes = new byte[stream.Length]; - // stream.Read(bytes, 0, (int)stream.Length); - - // var photoResponse = new PhotoResponse - // { - // Bytes = bytes, - // ContentType = response.Content.Headers.ContentType?.ToString(), - // }; - - // if (photoResponse != null) - // { - // photoResponse.Base64String = $"data:{photoResponse.ContentType};base64," + - // Convert.ToBase64String(photoResponse.Bytes); - // } - - // return photoResponse; - // } - // } - - /// - /// Gets an authenticated Microsoft Graph client using the token issued to the user. - /// - /// The authenticated GraphServiceClient. - public class SimpleAccessTokenProvider : IAccessTokenProvider - { - private readonly string _accessToken; - - public SimpleAccessTokenProvider(string accessToken) - { - _accessToken = accessToken; - } - - public Task GetAuthorizationTokenAsync(Uri uri, Dictionary context = null, CancellationToken cancellationToken = default) - { - return Task.FromResult(_accessToken); - } - - public AllowedHostsValidator AllowedHostsValidator => new AllowedHostsValidator(); - } - - private GraphServiceClient GetAuthenticatedClient() - { - var tokenProvider = new SimpleAccessTokenProvider(_token); - var authProvider = new BaseBearerTokenAuthenticationProvider(tokenProvider); - - return new GraphServiceClient(authProvider); - } - } -} diff --git a/samples/bot-teams-authentication/csharp/Startup.cs b/samples/bot-teams-authentication/csharp/Startup.cs deleted file mode 100644 index 99854118a7..0000000000 --- a/samples/bot-teams-authentication/csharp/Startup.cs +++ /dev/null @@ -1,83 +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.Bot.Builder.Teams; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Microsoft.BotBuilderSamples -{ - public class Startup - { - // 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(); - - // Create the Bot Adapter with error handling enabled. - services.AddSingleton(); - - // Create the storage we'll be using for User and Conversation state, as well as Single Sign On. - // (Memory is great for testing purposes.) - services.AddSingleton(); - - // For SSO, use CosmosDbPartitionedStorage - - /* COSMOSDB STORAGE - Uncomment the code in this section to use CosmosDB storage */ - - // var cosmosDbStorageOptions = new CosmosDbPartitionedStorageOptions() - // { - // CosmosDbEndpoint = "", - // AuthKey = "", - // DatabaseId = "", - // ContainerId = "" - // }; - // var storage = new CosmosDbPartitionedStorage(cosmosDbStorageOptions); - - /* END COSMOSDB STORAGE */ - - // Create the User state. (Used in this bot's Dialog implementation.) - services.AddSingleton(); - - // Create the Conversation state. (Used by the Dialog system itself.) - services.AddSingleton(); - - // The Dialog that will be run by the bot. - 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-teams-authentication/csharp/TeamsAuth.csproj b/samples/bot-teams-authentication/csharp/TeamsAuth.csproj deleted file mode 100644 index 2a1ad11036..0000000000 --- a/samples/bot-teams-authentication/csharp/TeamsAuth.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - net6.0 - latest - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - diff --git a/samples/bot-teams-authentication/csharp/TeamsAuth.sln b/samples/bot-teams-authentication/csharp/TeamsAuth.sln deleted file mode 100644 index f7a3e30573..0000000000 --- a/samples/bot-teams-authentication/csharp/TeamsAuth.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}") = "TeamsAuth", "TeamsAuth.csproj", "{BF151DD6-19D9-4205-B6A4-0E442641DE36}" -EndProject -Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{E198F1D5-2539-42D8-833A-CA2B18E8534B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F78B6736-919E-4D9C-B123-41397CB57178}" - ProjectSection(SolutionItems) = preProject - TeamsAuth.slnLaunch.user = TeamsAuth.slnLaunch.user - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BF151DD6-19D9-4205-B6A4-0E442641DE36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BF151DD6-19D9-4205-B6A4-0E442641DE36}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BF151DD6-19D9-4205-B6A4-0E442641DE36}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BF151DD6-19D9-4205-B6A4-0E442641DE36}.Release|Any CPU.Build.0 = Release|Any CPU - {E198F1D5-2539-42D8-833A-CA2B18E8534B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E198F1D5-2539-42D8-833A-CA2B18E8534B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E198F1D5-2539-42D8-833A-CA2B18E8534B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {E198F1D5-2539-42D8-833A-CA2B18E8534B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E198F1D5-2539-42D8-833A-CA2B18E8534B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {C097BF47-C494-4B93-B1CC-0CE6C2A4CE4F} - EndGlobalSection -EndGlobal diff --git a/samples/bot-teams-authentication/csharp/TeamsAuth.slnLaunch.user b/samples/bot-teams-authentication/csharp/TeamsAuth.slnLaunch.user index 4dc699e890..c3c992f6ea 100644 --- a/samples/bot-teams-authentication/csharp/TeamsAuth.slnLaunch.user +++ b/samples/bot-teams-authentication/csharp/TeamsAuth.slnLaunch.user @@ -1,31 +1,52 @@ [ { - "Name": "Microsoft Teams (browser)", + "Name": "Microsoft 365 Agents Playground (browser)", "Projects": [ { - "Path": "TeamsAuth.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": "TeamsAuth\\TeamsAuth.csproj", + "Name": "TeamsAuth\\TeamsAuth.csproj", + "Action": "Start", + "DebugTarget": "Microsoft 365 Agents Playground" } ] }, { - "Name": "Microsoft Teams (browser) (skip update app)", + "Name": "Microsoft Teams (browser)", "Projects": [ { - "Path": "TeamsAuth.csproj", + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft Teams (browser)" + }, + { + "Path": "TeamsAuth\\TeamsAuth.csproj", + "Name": "TeamsAuth\\TeamsAuth.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": "TeamsAuth\\TeamsAuth.csproj", + "Name": "TeamsAuth\\TeamsAuth.csproj", + "Action": "Start", + "DebugTarget": "Start Project" } ] } diff --git a/samples/bot-teams-authentication/csharp/TeamsAuth.slnx b/samples/bot-teams-authentication/csharp/TeamsAuth.slnx new file mode 100644 index 0000000000..b497259680 --- /dev/null +++ b/samples/bot-teams-authentication/csharp/TeamsAuth.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/samples/bot-teams-authentication/csharp/.gitignore b/samples/bot-teams-authentication/csharp/TeamsAuth/.gitignore similarity index 70% rename from samples/bot-teams-authentication/csharp/.gitignore rename to samples/bot-teams-authentication/csharp/TeamsAuth/.gitignore index 7466c01f9c..77c7154916 100644 --- a/samples/bot-teams-authentication/csharp/.gitignore +++ b/samples/bot-teams-authentication/csharp/TeamsAuth/.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-teams-authentication/csharp/TeamsAuth/Config.cs b/samples/bot-teams-authentication/csharp/TeamsAuth/Config.cs new file mode 100644 index 0000000000..3a14095a27 --- /dev/null +++ b/samples/bot-teams-authentication/csharp/TeamsAuth/Config.cs @@ -0,0 +1,16 @@ +namespace TeamsAuth +{ + public class ConfigOptions + { + public TeamsConfigOptions Teams { 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 string ConnectionName { get; set; } + } +} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/TeamsAuth/Program.cs b/samples/bot-teams-authentication/csharp/TeamsAuth/Program.cs new file mode 100644 index 0000000000..4d3e8cee48 --- /dev/null +++ b/samples/bot-teams-authentication/csharp/TeamsAuth/Program.cs @@ -0,0 +1,95 @@ +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Activities; +using Microsoft.Teams.Apps.Events; +using Microsoft.Teams.Apps.Extensions; +using Microsoft.Teams.Extensions.Graph; +using Microsoft.Teams.Common.Logging; +using Microsoft.Teams.Plugins.AspNetCore.Extensions; +using TeamsAuth; + +var builder = WebApplication.CreateBuilder(args); +var config = builder.Configuration.Get(); + + +var appBuilder = App.Builder() + .AddOAuth(config.Teams.ConnectionName); + +builder.AddTeams(appBuilder); + +var app = builder.Build(); +var teams =app.UseTeams(); +var token = ""; + + +teams.Use(async context => +{ + var start = DateTime.UtcNow; + try + { + await context.Next(); + } + catch + { + context.Log.Error("error occurred during activity processing"); + } + context.Log.Debug($"request took {(DateTime.UtcNow - start).TotalMilliseconds}ms"); +}); + +teams.OnMembersAdded(async context => +{ + await context.Send("Welcome to AuthenticationBot. Type '/signin' to get logged in. Type '/signout' to sign out."); +}); + + + +teams.OnMessage("/signout", async context => +{ + if (!context.IsSignedIn) + { + await context.Send("you are not signed in!"); + return; + } + + await context.SignOut(); + await context.Send("You have been signed out!"); +}); + +teams.OnMessage("/signin", async context => +{ + if (!context.IsSignedIn) + { + await context.SignIn(); + + return; + } + + var me = await context.GetUserGraphClient().Me.GetAsync(); + await context.Send($"User '{me!.DisplayName}' is already signed in!"); +}); + +teams.OnSignIn(async (_, @event) => +{ + token = @event.Token.Token; + var context = @event.Context; + + var me = await context.GetUserGraphClient().Me.GetAsync(); + await context.Send($"You have logged in as \"{me!.DisplayName}\". Would you like to view your token, type 'Yes' to view else type 'No' to cancel:"); +}); + +teams.OnMessage(async context => +{ + var command = context.Activity.Text.Trim().ToLowerInvariant(); + if (command.Equals("yes")) + { + await context.Send(token); + await context.Send("Thank You!"); + return; + } + else + { + await context.Send("Thank You!"); + return; + } +}); + +app.Run(); \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/TeamsAuth/Properties/launchSettings.json b/samples/bot-teams-authentication/csharp/TeamsAuth/Properties/launchSettings.json new file mode 100644 index 0000000000..3572a7a03f --- /dev/null +++ b/samples/bot-teams-authentication/csharp/TeamsAuth/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-teams-authentication/csharp/TeamsAuth/TeamsAuth.csproj b/samples/bot-teams-authentication/csharp/TeamsAuth/TeamsAuth.csproj new file mode 100644 index 0000000000..57ba05c7b4 --- /dev/null +++ b/samples/bot-teams-authentication/csharp/TeamsAuth/TeamsAuth.csproj @@ -0,0 +1,30 @@ + + + + net10.0 + enable + + + + + + + + + + + + + + + + PreserveNewest + None + + + + PreserveNewest + None + + + \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/TeamsAuth/appsettings.json b/samples/bot-teams-authentication/csharp/TeamsAuth/appsettings.json new file mode 100644 index 0000000000..9e3379db53 --- /dev/null +++ b/samples/bot-teams-authentication/csharp/TeamsAuth/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "" + } +} \ No newline at end of file diff --git a/samples/bot-teams-authentication/csharp/appsettings.Development.json b/samples/bot-teams-authentication/csharp/appsettings.Development.json deleted file mode 100644 index e203e9407e..0000000000 --- a/samples/bot-teams-authentication/csharp/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/samples/bot-teams-authentication/csharp/appsettings.json b/samples/bot-teams-authentication/csharp/appsettings.json deleted file mode 100644 index 9a9b2c33b6..0000000000 --- a/samples/bot-teams-authentication/csharp/appsettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "MicrosoftAppType": "", - "MicrosoftAppId": "", - "MicrosoftAppPassword": "", - "ConnectionName": "", - "UseSingleSignOn": true, - "MicrosoftAppTenantId": "" -} diff --git a/samples/bot-teams-authentication/csharp/wwwroot/default.htm b/samples/bot-teams-authentication/csharp/wwwroot/default.htm deleted file mode 100644 index d4a5f082b7..0000000000 --- a/samples/bot-teams-authentication/csharp/wwwroot/default.htm +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - Teams Sample - - - - - - - - - Teams Sample - - - - - Your bot is ready! - You can test your bot in the Bot Framework Emulator - by connecting to http://localhost:3978/api/messages. - Download the Emulator - 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 - - - - - - - - diff --git a/samples/meetings-sidepanel/csharp/M365Agent/.gitignore b/samples/meetings-sidepanel/csharp/M365Agent/.gitignore new file mode 100644 index 0000000000..c5cae9258c --- /dev/null +++ b/samples/meetings-sidepanel/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-teams-authentication/csharp/M365Agent/M365Agent.ttkproj b/samples/meetings-sidepanel/csharp/M365Agent/M365Agent.atkproj similarity index 51% rename from samples/bot-teams-authentication/csharp/M365Agent/M365Agent.ttkproj rename to samples/meetings-sidepanel/csharp/M365Agent/M365Agent.atkproj index 39e390e83a..124eb75046 100644 --- a/samples/bot-teams-authentication/csharp/M365Agent/M365Agent.ttkproj +++ b/samples/meetings-sidepanel/csharp/M365Agent/M365Agent.atkproj @@ -1,12 +1,8 @@ - + - e198f1d5-2539-42d8-833a-ca2b18e8534b + b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf - - - - diff --git a/samples/meetings-sidepanel/csharp/M365Agent/aad.manifest.json b/samples/meetings-sidepanel/csharp/M365Agent/aad.manifest.json deleted file mode 100644 index 8ba735fe42..0000000000 --- a/samples/meetings-sidepanel/csharp/M365Agent/aad.manifest.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "id": "${{AAD_APP_OBJECT_ID}}", - "appId": "${{AAD_APP_CLIENT_ID}}", - "name": "meetings-sidepanel-aad", - "accessTokenAcceptedVersion": 2, - "signInAudience": "AzureADMyOrg", - "oauth2AllowIdTokenImplicitFlow": true, - "oauth2AllowImplicitFlow": true, - "optionalClaims": { - "idToken": [], - "accessToken": [ - { - "name": "idtyp", - "source": null, - "essential": false, - "additionalProperties": [] - } - ], - "saml2Token": [] - }, - "requiredResourceAccess": [ - { - "resourceAppId": "Microsoft Graph", - "resourceAccess": [ - { - "id": "User.Read", - "type": "Scope" - } - ] - } - ] -} \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/M365Agent/appPackage/color.png b/samples/meetings-sidepanel/csharp/M365Agent/appPackage/color.png index b8cf81afbe..01aa37e347 100644 Binary files a/samples/meetings-sidepanel/csharp/M365Agent/appPackage/color.png and b/samples/meetings-sidepanel/csharp/M365Agent/appPackage/color.png differ diff --git a/samples/meetings-sidepanel/csharp/M365Agent/appPackage/manifest.json b/samples/meetings-sidepanel/csharp/M365Agent/appPackage/manifest.json index 50db90ab2d..b6063d27b1 100644 --- a/samples/meetings-sidepanel/csharp/M365Agent/appPackage/manifest.json +++ b/samples/meetings-sidepanel/csharp/M365Agent/appPackage/manifest.json @@ -24,7 +24,7 @@ "accentColor": "#5558AF", "bots": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "${{BOT_ID}}", "scopes": [ "groupChat" ], diff --git a/samples/meetings-sidepanel/csharp/M365Agent/appPackage/outline.png b/samples/meetings-sidepanel/csharp/M365Agent/appPackage/outline.png index 77f195a522..f7a4c86447 100644 Binary files a/samples/meetings-sidepanel/csharp/M365Agent/appPackage/outline.png and b/samples/meetings-sidepanel/csharp/M365Agent/appPackage/outline.png differ diff --git a/samples/meetings-sidepanel/csharp/M365Agent/env/.env.dev b/samples/meetings-sidepanel/csharp/M365Agent/env/.env.dev new file mode 100644 index 0000000000..df4f9da508 --- /dev/null +++ b/samples/meetings-sidepanel/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/meetings-sidepanel/csharp/M365Agent/env/.env.local b/samples/meetings-sidepanel/csharp/M365Agent/env/.env.local index 76ee8a0ba6..86dfacc35a 100644 --- a/samples/meetings-sidepanel/csharp/M365Agent/env/.env.local +++ b/samples/meetings-sidepanel/csharp/M365Agent/env/.env.local @@ -7,17 +7,8 @@ APP_NAME_SUFFIX=local # Generated during provision, you can also add your own variables. BOT_ID= TEAMS_APP_ID= -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= +BOT_OBJECT_ID= TEAMSFX_M365_USER_NAME= BOT_ENDPOINT= diff --git a/samples/meetings-sidepanel/csharp/M365Agent/infra/azure.bicep b/samples/meetings-sidepanel/csharp/M365Agent/infra/azure.bicep index c3ce051b3d..658e412a21 100644 --- a/samples/meetings-sidepanel/csharp/M365Agent/infra/azure.bicep +++ b/samples/meetings-sidepanel/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/meetings-sidepanel/csharp/M365Agent/infra/azure.parameters.json b/samples/meetings-sidepanel/csharp/M365Agent/infra/azure.parameters.json index 208439e294..1f908ec79e 100644 --- a/samples/meetings-sidepanel/csharp/M365Agent/infra/azure.parameters.json +++ b/samples/meetings-sidepanel/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": "meetings-sidepanel" - }, - "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": "SidePanel" + } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/M365Agent/infra/botRegistration/azurebot.bicep b/samples/meetings-sidepanel/csharp/M365Agent/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..a5a27b8fe4 --- /dev/null +++ b/samples/meetings-sidepanel/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/meetings-sidepanel/csharp/M365Agent/infra/botRegistration/readme.md b/samples/meetings-sidepanel/csharp/M365Agent/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/meetings-sidepanel/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/meetings-sidepanel/csharp/M365Agent/launchSettings.json b/samples/meetings-sidepanel/csharp/M365Agent/launchSettings.json index 8c76c70d9e..2af8ce7a8a 100644 --- a/samples/meetings-sidepanel/csharp/M365Agent/launchSettings.json +++ b/samples/meetings-sidepanel/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/meetings-sidepanel/csharp/M365Agent/m365agents.local.yml b/samples/meetings-sidepanel/csharp/M365Agent/m365agents.local.yml index cb912ec22a..277034f292 100644 --- a/samples/meetings-sidepanel/csharp/M365Agent/m365agents.local.yml +++ b/samples/meetings-sidepanel/csharp/M365Agent/m365agents.local.yml @@ -1,80 +1,67 @@ -# 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:meetings-sidepanel-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: meetings-sidepanel-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: meetings-sidepanel-${{TEAMSFX_ENV}} + name: SidePanel${{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: SidePanel${{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: ../SidePanel/appsettings.json + target: ../SidePanel/appsettings.Development.json content: - MicrosoftAppId: ${{AAD_APP_CLIENT_ID}} - MicrosoftAppPassword: ${{SECRET_AAD_APP_CLIENT_SECRET}} - BaseUrl: ${{BOT_ENDPOINT}} - MicrosoftAppType: ${{MICROSOFT_APP_TYPE}} - MicrosoftAppTenantId: ${{MICROSOFT_APP_TENANT_ID}} + Teams: + ClientId: ${{BOT_ID}} + ClientSecret: ${{SECRET_BOT_PASSWORD}} + TenantId: ${{TEAMS_APP_TENANT_ID}} - - uses: arm/deploy # Deploy given ARM templates parallelly. - 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 # 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. + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create 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: SidePanel + 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: @@ -88,9 +75,3 @@ provision: with: # Relative path to this file. This is the path for built zip file. appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - - # Run npm command for client - - uses: cli/runNpmCommand - with: - workingDirectory: ../SidePanel/ClientApp - args: install --no-audit \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/M365Agent/m365agents.yml b/samples/meetings-sidepanel/csharp/M365Agent/m365agents.yml index 37e45fe7cc..3cc2afedcb 100644 --- a/samples/meetings-sidepanel/csharp/M365Agent/m365agents.yml +++ b/samples/meetings-sidepanel/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:meetings-sidepanel-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: SidePanel${{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 + SidePanel.csproj + workingDirectory: ../SidePanel + # 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: ../SidePanel +projectId: bc93a237-980d-4dd3-a3fd-e12365d09e75 diff --git a/samples/meetings-sidepanel/csharp/README.md b/samples/meetings-sidepanel/csharp/README.md index 3828870c8f..03ac53ce15 100644 --- a/samples/meetings-sidepanel/csharp/README.md +++ b/samples/meetings-sidepanel/csharp/README.md @@ -110,14 +110,14 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool **Note** : In the debug dropdown menu of Visual Studio, select default startup project > **SidePanel** 6. Setup and run the bot from Visual Studio: - Modify the `appsettings.json` and fill in the following details: - - `<>` - Generated from Step 2 (Application (client) ID) is the application app id - - `<>` - Generated from Step 3, also referred to as Client secret - - `<>` - 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 2 (Application (client) ID) is the application app id + - `ClientSecret` - Generated from Step 3, also referred to as Client secret + - `BaseUrl` - 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. 7. Modify the `manifest.json` in the `/appPackage` folder and replace the following details: - - <> with any random GUID or your MicrosoftAppId from Microsoft Entra ID app registration. - - `<>` with Application id generated from Step 3 + - `TEAMS_APP_ID` with any random GUID or your MicrosoftAppId from Microsoft Entra ID app registration. + - `BOT_ID` with Application id generated from Step 3 - `{{Base_URL}}` 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`. - `{{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`. @@ -127,15 +127,12 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool ```bash npm install - npm start ``` 10. Upload the manifest.zip to Teams (in the Apps view click "Upload a custom app") - Go to Microsoft Teams. From the lower left corner, select Apps - From the lower left corner, choose Upload a custom App - Go to your project directory, the ./appPackage folder, select the zip folder, and choose Open. -**Note**: If you are facing any issue in your app, [please uncomment this line](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/meetings-sidepanel/csharp/SidePanel/AdapterWithErrorHandler.cs#L26) and put your debugger for local debug. - ## Running the sample User interactions(Meeting Organizer) - **Add New Agenda Item** - Gives provision to add new Agenda point. diff --git a/samples/meetings-sidepanel/csharp/SidePanel.sln b/samples/meetings-sidepanel/csharp/SidePanel.sln deleted file mode 100644 index 4191b93bbb..0000000000 --- a/samples/meetings-sidepanel/csharp/SidePanel.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.10.35004.147 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SidePanel", "SidePanel\SidePanel.csproj", "{8BB4E740-A247-4BA2-A535-5ABEBFB9EAAF}" -EndProject -Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{F3CD3E47-E68E-4923-93E3-44DF8A1AFA6A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F8822451-9F5F-4FDD-BDC4-895E0A325EAB}" - ProjectSection(SolutionItems) = preProject - SidePanel.slnLaunch.user = SidePanel.slnLaunch.user - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8BB4E740-A247-4BA2-A535-5ABEBFB9EAAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8BB4E740-A247-4BA2-A535-5ABEBFB9EAAF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8BB4E740-A247-4BA2-A535-5ABEBFB9EAAF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8BB4E740-A247-4BA2-A535-5ABEBFB9EAAF}.Release|Any CPU.Build.0 = Release|Any CPU - {F3CD3E47-E68E-4923-93E3-44DF8A1AFA6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3CD3E47-E68E-4923-93E3-44DF8A1AFA6A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3CD3E47-E68E-4923-93E3-44DF8A1AFA6A}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {F3CD3E47-E68E-4923-93E3-44DF8A1AFA6A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3CD3E47-E68E-4923-93E3-44DF8A1AFA6A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1546F593-46BD-4110-B55A-D3315812919E} - EndGlobalSection -EndGlobal diff --git a/samples/meetings-sidepanel/csharp/SidePanel.slnLaunch.user b/samples/meetings-sidepanel/csharp/SidePanel.slnLaunch.user index 21dbbe2f4d..1b49a07d2b 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel.slnLaunch.user +++ b/samples/meetings-sidepanel/csharp/SidePanel.slnLaunch.user @@ -1,31 +1,52 @@ [ { - "Name": "Microsoft Teams (browser)", + "Name": "Microsoft 365 Agents Playground (browser)", "Projects": [ { - "Path": "SidePanel\\SidePanel.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": "SidePanel\\SidePanel.csproj", + "Name": "SidePanel\\SidePanel.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": "SidePanel\\SidePanel.csproj", + "Name": "SidePanel\\SidePanel.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": "SidePanel\\SidePanel.csproj", + "Name": "SidePanel\\SidePanel.csproj", + "Action": "Start", + "DebugTarget": "Start Project" } ] } diff --git a/samples/meetings-sidepanel/csharp/SidePanel.slnx b/samples/meetings-sidepanel/csharp/SidePanel.slnx new file mode 100644 index 0000000000..c5aca5f200 --- /dev/null +++ b/samples/meetings-sidepanel/csharp/SidePanel.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/samples/meetings-sidepanel/csharp/SidePanel/.gitignore b/samples/meetings-sidepanel/csharp/SidePanel/.gitignore index 7466c01f9c..77c7154916 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/.gitignore +++ b/samples/meetings-sidepanel/csharp/SidePanel/.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/meetings-sidepanel/csharp/SidePanel/AdapterWithErrorHandler.cs b/samples/meetings-sidepanel/csharp/SidePanel/AdapterWithErrorHandler.cs deleted file mode 100644 index c278c7483e..0000000000 --- a/samples/meetings-sidepanel/csharp/SidePanel/AdapterWithErrorHandler.cs +++ /dev/null @@ -1,33 +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.Configuration; -using Microsoft.Extensions.Logging; - -namespace Microsoft.BotBuilderSamples -{ - public class AdapterWithErrorHandler : CloudAdapter - { - public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger) - : base(configuration, null, 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 turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); - }; - } - } -} \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Bots/MeetingBot.cs b/samples/meetings-sidepanel/csharp/SidePanel/Bots/MeetingBot.cs deleted file mode 100644 index 450861282c..0000000000 --- a/samples/meetings-sidepanel/csharp/SidePanel/Bots/MeetingBot.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Teams; -using Microsoft.Bot.Schema; -using SidePanel.Controllers; - -namespace Microsoft.BotBuilderSamples.Bots -{ - public class MeetingBot : TeamsActivityHandler - { - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - HomeController.serviceUrl = turnContext.Activity.ServiceUrl; - HomeController.conversationId = turnContext.Activity.Conversation.Id; - var replyText = "Hello and welcome **" + turnContext.Activity.From.Name + "** to the Meeting Extensibility SidePanel app."; - await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken); - } - - protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) - { - var welcomeText = "Hello and welcome " + turnContext.Activity.From.Name + " to the Meeting Extensibility SidePanel app."; - foreach (var member in membersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken); - } - } - } - - protected override async Task OnConversationUpdateActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - HomeController.serviceUrl = turnContext.Activity.ServiceUrl; - HomeController.conversationId = turnContext.Activity.Conversation.Id; - await base.OnConversationUpdateActivityAsync(turnContext, cancellationToken); - } - } -} \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/.env b/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/.env new file mode 100644 index 0000000000..5650bec32a --- /dev/null +++ b/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/.env @@ -0,0 +1,2 @@ +PORT=3000 +BROWSER=none diff --git a/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/.vscode/settings.json b/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/.vscode/settings.json deleted file mode 100644 index 139b3d28f8..0000000000 --- a/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/.vscode/settings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "editor.tabSize": 2, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "vscode.typescript-language-features" - } - } - \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/package-lock.json b/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/package-lock.json index d9da6002ad..7d7f4fc22e 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/package-lock.json +++ b/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/package-lock.json @@ -8878,6 +8878,13 @@ "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "license": "MIT" }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/q": { "version": "1.5.8", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", @@ -8896,6 +8903,18 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, + "node_modules/@types/react": { + "version": "17.0.90", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.90.tgz", + "integrity": "sha512-P9beVR/x06U9rCJzSxtENnOr4BrbJ6VrsrDTc+73TtHv9XHhryXKbjGRB+6oooB2r0G/pQkD/S4dHo/7jUfwFw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.2.2" + } + }, "node_modules/@types/react-dom": { "version": "17.0.26", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz", @@ -8905,6 +8924,13 @@ "@types/react": "^17.0.0" } }, + "node_modules/@types/react/node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT", + "peer": true + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -8920,6 +8946,13 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "license": "MIT" }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "license": "MIT", + "peer": true + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -12179,6 +12212,16 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -14448,6 +14491,13 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/free-style": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/free-style/-/free-style-3.1.0.tgz", + "integrity": "sha512-vJujYSIyT30iDoaoeigNAxX4yB1RUrh+N2ZMhIElMr3BvCuGXOw7XNJMEEJkDUeamK2Rnb/IKFGKRKlTWIGRWA==", + "license": "MIT", + "peer": true + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -18944,6 +18994,19 @@ "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", "dev": true }, + "node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -19145,6 +19208,17 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "license": "MIT", + "peer": true, + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } + }, "node_modules/mousetrap": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", @@ -19174,31 +19248,31 @@ "react": "^16.7.0" } }, - "node_modules/msteams-ui-icons-core": { + "node_modules/msteams-ui-components-react/node_modules/msteams-ui-icons-react": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/msteams-ui-icons-core/-/msteams-ui-icons-core-0.4.2.tgz", - "integrity": "sha512-jaHadyxAuSwvopQjcMSjlkVB1B2z4Ln5XE683d0unfDtrlL9jSczHRfCQoSVHE4khjtBG/4HU92zf+SbmUGzkQ==", + "resolved": "https://registry.npmjs.org/msteams-ui-icons-react/-/msteams-ui-icons-react-0.4.2.tgz", + "integrity": "sha512-ftm0DMeIdTDnGG22up6yDkBE16JOc+LoWNUkunjI74IwKcltWCVIbhJzxKyCCecFrnKEqSxzKy4gToW1Ea1k9w==", "license": "MIT", "dependencies": { - "lodash.memoize": "^4.1.2" + "msteams-ui-icons-core": "^0.4.2" }, "peerDependencies": { + "@types/prop-types": "^15.5.8", + "@types/react": "^16.7.17", + "prop-types": "^15.6.2", + "react": "^16.7.0", "typestyle": "^2.0.1" } }, - "node_modules/msteams-ui-icons-react": { + "node_modules/msteams-ui-icons-core": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/msteams-ui-icons-react/-/msteams-ui-icons-react-0.4.2.tgz", - "integrity": "sha512-ftm0DMeIdTDnGG22up6yDkBE16JOc+LoWNUkunjI74IwKcltWCVIbhJzxKyCCecFrnKEqSxzKy4gToW1Ea1k9w==", + "resolved": "https://registry.npmjs.org/msteams-ui-icons-core/-/msteams-ui-icons-core-0.4.2.tgz", + "integrity": "sha512-jaHadyxAuSwvopQjcMSjlkVB1B2z4Ln5XE683d0unfDtrlL9jSczHRfCQoSVHE4khjtBG/4HU92zf+SbmUGzkQ==", "license": "MIT", "dependencies": { - "msteams-ui-icons-core": "^0.4.2" + "lodash.memoize": "^4.1.2" }, "peerDependencies": { - "@types/prop-types": "^15.5.8", - "@types/react": "^16.7.17", - "prop-types": "^15.6.2", - "react": "^16.7.0", "typestyle": "^2.0.1" } }, @@ -24489,6 +24563,38 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/typestyle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/typestyle/-/typestyle-2.4.0.tgz", + "integrity": "sha512-/d1BL6Qi+YlMLEydnUEB8KL/CAjAN8cyt3/UyGnOyBrWf7bLGcR/6yhmsaUstO2IcYwZfagjE7AIzuI2vUW9mg==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "3.0.10", + "free-style": "3.1.0" + } + }, + "node_modules/typestyle/node_modules/csstype": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", + "license": "MIT", + "peer": true + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/package.json b/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/package.json index 969a13c46f..b6a0569e27 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/package.json +++ b/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/package.json @@ -37,8 +37,8 @@ }, "scripts": { "clean": "npx shx rm -rf build", - "start": "react-scripts start", - "start:client": "react-scripts start", + "start": "set PORT=3000 && react-scripts start", + "start:client": "set PORT=3000 && react-scripts start", "start:server": "npx @fluidframework/azure-local-service@latest", "build": "react-scripts build", "test": "react-scripts test", diff --git a/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/src/pages/SidePanel.jsx b/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/src/pages/SidePanel.jsx index b4cc72ce60..576f067eca 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/src/pages/SidePanel.jsx +++ b/samples/meetings-sidepanel/csharp/SidePanel/ClientApp/src/pages/SidePanel.jsx @@ -14,8 +14,18 @@ let containerValue; const SidePanel = (props) => { - const agendaValueKey = "editor-value-key"; - const [appTheme, setAppTheme] = useState(""); +const agendaValueKey = "editor-value-key"; +const [appTheme, setAppTheme] = useState(""); +const [isFluidReady, setIsFluidReady] = useState(false); +const [notification, setNotification] = useState({ show: false, message: '', type: '' }); + +// Helper function to show notifications +const showNotification = (message, type = 'info') => { + setNotification({ show: true, message, type }); + setTimeout(() => { + setNotification({ show: false, message: '', type: '' }); + }, 5000); // Auto-hide after 5 seconds +}; useEffect(() => { microsoftTeams.app.initialize().then(() => { @@ -36,20 +46,46 @@ const SidePanel = (props) => { } var userId = context.user.id; - var meetingId = context.meeting.id; + var meetingId = context.meeting?.id; var tenantId = context.user.tenant.id; + var conversationId = context.chat?.id || context.channel?.id || context.meeting?.conversationId; + + // Try to initialize conversation context from Teams SDK + if (conversationId) { + console.log('Initializing conversation from Teams context:', conversationId); + fetch(`${window.location.origin}/Home/InitializeConversation`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + conversationId: conversationId, + serviceUrl: 'https://smba.trafficmanager.net/amer/' // Default service URL + }) + }).then(response => { + if (response.ok) { + console.log('Conversation initialized successfully'); + } + }).catch(err => console.error('Error initializing conversation:', err)); + } + agendaListPopulate(); - setMeetingContext(userId, meetingId, tenantId).then((result) => { - if (result.data == true) { + if (meetingId) { + setMeetingContext(userId, meetingId, tenantId).then((result) => { + if (result.data == true) { + document.getElementById("agendaButtonDiv").style.display = "block"; + document.getElementById("publishAgendaButton").style.display = "block"; + } + else { + document.getElementById("agendaButtonDiv").style.display = "none"; + document.getElementById("publishAgendaButton").style.display = "none"; + } + }).catch(err => { + console.error('Error checking organizer role:', err); + // Show agenda button anyway if role check fails document.getElementById("agendaButtonDiv").style.display = "block"; document.getElementById("publishAgendaButton").style.display = "block"; - } - else { - document.getElementById("agendaButtonDiv").style.display = "none"; - document.getElementById("publishAgendaButton").style.display = "none"; - } - }) + }); + } }); // Handle app theme when 'Teams' theme changes @@ -77,25 +113,40 @@ const SidePanel = (props) => { let connection; window.localStorage.debug = "fluid:*"; - await microsoftTeams.app.initialize(); - const host = LiveShareHost.create(); - - // Define Fluid document schema and create container - const client = new LiveShareClient(host); - const containerSchema = { - initialObjects: { editorMap: SharedMap } - }; - - function onContainerFirstCreated(container) { - // Set initial state of the editorMap. - var initalArray = ["Approve 5% dividend payment to shareholders.", "Increase research budget by 10%.", "Continue with WFH for next 3 months."]; - container.initialObjects.editorMap.set(agendaValueKey, initalArray); - } + + try { + await microsoftTeams.app.initialize(); + const host = LiveShareHost.create(); + + // Define Fluid document schema and create container + const client = new LiveShareClient(host); + const containerSchema = { + initialObjects: { editorMap: SharedMap } + }; - // Joining the container with default schema defined. - const { container } = await client.joinContainer(containerSchema, onContainerFirstCreated); - containerValue = container; - containerValue.initialObjects.editorMap.on("valueChanged", updateEditorState); + function onContainerFirstCreated(container) { + // Set initial state of the editorMap. + var initalArray = ["Approve 5% dividend payment to shareholders.", "Increase research budget by 10%.", "Continue with WFH for next 3 months."]; + container.initialObjects.editorMap.set(agendaValueKey, initalArray); + } + + // Joining the container with default schema defined. + const { container } = await client.joinContainer(containerSchema, onContainerFirstCreated); + containerValue = container; + containerValue.initialObjects.editorMap.on("valueChanged", updateEditorState); + + // Mark Fluid as ready + setIsFluidReady(true); + console.log('Fluid container initialized successfully'); + + // Update the agenda list display + agendaListPopulate(); + } catch (error) { + console.error('Error initializing Fluid container:', error); + setIsFluidReady(false); + // Still show default agenda even if Fluid fails + agendaListPopulate(); + } })(); @@ -129,23 +180,74 @@ const SidePanel = (props) => { document.getElementById("agendaInputDiv").style.display = "none"; document.getElementById("agendaButtonDiv").style.display = "block"; var newAgendaItem = document.getElementById('agendaInput').value; + + if (!newAgendaItem || newAgendaItem.trim() === '') { + showNotification('Please enter an agenda item.', 'warning'); + document.getElementById("agendaInputDiv").style.display = "block"; + document.getElementById("agendaButtonDiv").style.display = "none"; + document.getElementById("agendaInput").focus(); + return; + } + let taskInfo = { title: newAgendaItem }; // API call to save agenda. - addAgendaTask(taskInfo); + addAgendaTask(taskInfo) + .then(() => { + showNotification('Agenda item added successfully!', 'success'); + }) + .catch(err => { + console.error('Error adding agenda task:', err); + showNotification('Failed to save agenda item.', 'error'); + }); + + // Update Fluid container if initialized + if (containerValue && containerValue.initialObjects && containerValue.initialObjects.editorMap) { + var editorMap = containerValue.initialObjects.editorMap; + var agendas = editorMap.get(agendaValueKey) || []; + agendas.push(newAgendaItem); + editorMap.set(agendaValueKey, agendas); + } else { + // Fallback: if Fluid is not ready, just update locally + console.warn('Fluid container not ready, agenda will be added on next sync'); + agendaListPopulate(); + } - var editorMap = containerValue.initialObjects.editorMap; - var agendas = editorMap.get(agendaValueKey); - agendas.push(newAgendaItem); - editorMap.set(agendaValueKey, agendas); + // Clear input + document.getElementById('agendaInput').value = ''; } // This method is called to publish the agenda in meeting chat. function publishAgenda() { + // Check if container is initialized + if (!containerValue || !containerValue.initialObjects || !containerValue.initialObjects.editorMap) { + showNotification('Loading agenda data, please wait a moment and try again.', 'warning'); + return; + } + const agendaValue = containerValue.initialObjects.editorMap.get(agendaValueKey); - postAgenda(agendaValue); + + postAgenda(agendaValue) + .then((response) => { + showNotification('Agenda published successfully to meeting chat!', 'success'); + }) + .catch((error) => { + console.error('Error publishing agenda:', error); + if (error.response && error.response.data) { + const errorData = error.response.data; + if (errorData.message) { + showNotification(errorData.message, 'error'); + } else if (typeof errorData === 'string') { + showNotification(errorData, 'error'); + } else { + showNotification('Error publishing agenda. Please send a message in the chat first to initialize the bot.', 'error'); + } + } else { + showNotification('Error publishing agenda. Please make sure you are in a meeting and try again.', 'error'); + } + }); } // This method is called whenever the shared state is updated. @@ -160,6 +262,63 @@ const SidePanel = (props) => { Agenda + + {/* Notification Banner */} + {notification.show && ( + + + {notification.type === 'success' && '? '} + {notification.type === 'error' && '? '} + {notification.type === 'warning' && '? '} + {notification.type === 'info' && '? '} + {notification.message} + + setNotification({ show: false, message: '', type: '' })} + style={{ + background: 'none', + border: 'none', + fontSize: '18px', + cursor: 'pointer', + padding: '0 5px', + color: 'inherit' + }} + > + × + + + )} + + {/* Loading Indicator */} + {!isFluidReady && ( + + ? Loading collaboration features... + + )} + Add New Agenda Item @@ -171,7 +330,13 @@ const SidePanel = (props) => { - Publish Agenda + + Publish Agenda {!isFluidReady && '(Loading...)'} + > ); diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Config.cs b/samples/meetings-sidepanel/csharp/SidePanel/Config.cs new file mode 100644 index 0000000000..6a4fadd99a --- /dev/null +++ b/samples/meetings-sidepanel/csharp/SidePanel/Config.cs @@ -0,0 +1,16 @@ +namespace SidePanel +{ + public class ConfigOptions + { + public TeamsConfigOptions Teams { get; set; } = new(); + public string BaseUrl { get; set; } = string.Empty; + } + + public class TeamsConfigOptions + { + public string BotType { get; set; } = string.Empty; + public string ClientId { get; set; } = string.Empty; + public string ClientSecret { get; set; } = string.Empty; + public string TenantId { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Controllers/BotController.cs b/samples/meetings-sidepanel/csharp/SidePanel/Controllers/BotController.cs deleted file mode 100644 index 07b6832cda..0000000000 --- a/samples/meetings-sidepanel/csharp/SidePanel/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, 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); - } - } -} \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Controllers/Controller.cs b/samples/meetings-sidepanel/csharp/SidePanel/Controllers/Controller.cs new file mode 100644 index 0000000000..8f48896e10 --- /dev/null +++ b/samples/meetings-sidepanel/csharp/SidePanel/Controllers/Controller.cs @@ -0,0 +1,63 @@ +using Microsoft.Teams.Api.Activities; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Activities; +using Microsoft.Teams.Apps.Annotations; + +namespace SidePanel.Controllers +{ + [TeamsController] + public class Controller() + { + [Message] + public async Task OnMessage([Context] MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + // Store conversation details for proactive messaging + HomeController.serviceUrl = activity.ServiceUrl; + HomeController.conversationId = activity.Conversation.Id; + + var replyText = $"Hello and welcome **{activity.From.Name}** to the Meeting Extensibility SidePanel app."; + + log.Info($"Message received from {activity.From.Name}"); + await client.Send(replyText); + } + + [Conversation.MembersAdded] + public async Task OnMembersAdded(IContext context) + { + // Store conversation details immediately when members are added + HomeController.serviceUrl = context.Activity.ServiceUrl; + HomeController.conversationId = context.Activity.Conversation.Id; + + var welcomeText = $"Hello and welcome to the Meeting Extensibility SidePanel app."; + foreach (var member in context.Activity.MembersAdded) + { + if (member.Id != context.Activity.Recipient.Id) + { + await context.Send(welcomeText); + } + } + } + + [Conversation.Update] + public Task OnConversationUpdate([Context] ConversationUpdateActivity activity, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + // Store conversation details for proactive messaging + HomeController.serviceUrl = activity.ServiceUrl; + HomeController.conversationId = activity.Conversation.Id; + + log.Info($"Conversation updated: {activity.Conversation.Id}, ServiceUrl: {activity.ServiceUrl}"); + return Task.CompletedTask; + } + + [Install] + public Task OnInstall([Context] InstallUpdateActivity activity, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + // Store conversation details when app is installed + HomeController.serviceUrl = activity.ServiceUrl; + HomeController.conversationId = activity.Conversation.Id; + + log.Info($"App installed in conversation: {activity.Conversation.Id}, ServiceUrl: {activity.ServiceUrl}"); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Controllers/HomeController.cs b/samples/meetings-sidepanel/csharp/SidePanel/Controllers/HomeController.cs index c16c6e216d..16a5ba18c3 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/Controllers/HomeController.cs +++ b/samples/meetings-sidepanel/csharp/SidePanel/Controllers/HomeController.cs @@ -1,23 +1,18 @@ -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using Microsoft.Bot.Connector; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Bot.Schema; -using Microsoft.Extensions.Configuration; -using AdaptiveCards; -using System.Threading.Tasks; -using System.Net.Http; -using System.Net.Http.Headers; -using Newtonsoft.Json; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Teams.Cards; using SidePanel.Models; +using System.Net.Http.Headers; +using System.Text.Json; +using Microsoft.Teams.Apps; namespace SidePanel.Controllers { - public class HomeController : Controller + [Route("[controller]")] + [ApiController] + public class HomeController : ControllerBase { - public static string conversationId; - public static string serviceUrl; + public static string? conversationId; + public static string? serviceUrl; public static List taskInfoData = new List() { new TaskInfo(){Title = "Approve 5% dividend payment to shareholders."}, @@ -26,93 +21,196 @@ public class HomeController : Controller }; private readonly IConfiguration _configuration; - private readonly AppCredentials botCredentials; - private readonly HttpClient httpClient; + private readonly IHttpClientFactory _httpClientFactory; + private readonly App _app; - public HomeController(IConfiguration configuration, IHttpClientFactory httpClientFactory, AppCredentials botCredentials) + public HomeController( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + App app) { _configuration = configuration; - this.botCredentials = botCredentials; - this.httpClient = httpClientFactory.CreateClient(); + _httpClientFactory = httpClientFactory; + _app = app; } - //Add New Agenda Point to the Agenda List - [Route("/Home/AddNewAgendaPoint")] + // Add New Agenda Point to the Agenda List + [Route("AddNewAgendaPoint")] [HttpPost] - public List AddNewAgendaPoint([FromBody] TaskInfo taskInfo) + public ActionResult> AddNewAgendaPoint([FromBody] TaskInfo taskInfo) { var tData = new TaskInfo { Title = taskInfo.Title }; taskInfoData.Add(tData); - return taskInfoData; + return Ok(taskInfoData); } - //Senda Agenda List to the Meeting Chat - [Route("/Home/SendAgenda")] - public void SendAgenda() + // Send Agenda List to the Meeting Chat + [Route("SendAgenda")] + [HttpGet] + public async Task SendAgenda() { - string appId = _configuration["MicrosoftAppId"]; - string appSecret = _configuration["MicrosoftAppPassword"]; - using var connector = new ConnectorClient(new Uri(serviceUrl), appId, appSecret); - MicrosoftAppCredentials.TrustServiceUrl(serviceUrl, DateTime.MaxValue); - var replyActivity = new Activity(); - replyActivity.Type = "message"; - replyActivity.Conversation = new ConversationAccount(id: conversationId); - var adaptiveAttachment = AgendaAdaptiveList(); - replyActivity.Attachments = new List { adaptiveAttachment }; - var response = connector.Conversations.SendToConversationAsync(conversationId, replyActivity).Result; + if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(serviceUrl)) + { + return BadRequest(new { + error = "Conversation not initialized", + message = "Please send a message in the chat first to initialize the bot connection." + }); + } + + try + { + var adaptiveCard = CreateAgendaAdaptiveCard(); + + // Use the new Teams SDK to send the message + await _app.Send(conversationId, adaptiveCard); + + return Ok(new { message = "Agenda published successfully" }); + } + catch (Exception ex) + { + return StatusCode(500, new { error = "Error sending agenda", message = ex.Message }); + } } - //Create Adaptive Card with the Agenda List - private Attachment AgendaAdaptiveList() + // Create Adaptive Card with the Agenda List + private AdaptiveCard CreateAgendaAdaptiveCard() { - AdaptiveCard adaptiveCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0)); - adaptiveCard.Body = new List() + var card = new AdaptiveCard { - new AdaptiveTextBlock(){Text="Here is the Agenda for Today", Weight=AdaptiveTextWeight.Bolder} + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("Here is the Agenda for Today") + { + Weight = TextWeight.Bolder + } + } }; foreach (var agendaPoint in taskInfoData) { - var textBlock = new AdaptiveTextBlock() { Text = "- " + agendaPoint.Title + " \r" }; - adaptiveCard.Body.Add(textBlock); + var textBlock = new TextBlock($"- {agendaPoint.Title}") + { + Wrap = true + }; + card.Body.Add(textBlock); } - return new Attachment() - { - ContentType = AdaptiveCard.ContentType, - Content = adaptiveCard - }; + return card; } - //Check if the Participant Role is Organizer - [Route("/Home/IsOrganizer")] + // Check if the Participant Role is Organizer + [Route("IsOrganizer")] + [HttpGet] public async Task> IsOrganizer(string userId, string meetingId, string tenantId) { - var response = await GetMeetingRoleAsync(meetingId, userId, tenantId); - if (response.meeting.role == "Organizer") - return true; - else - return false; + try + { + var response = await GetMeetingRoleAsync(meetingId, userId, tenantId); + return Ok(response?.meeting?.role == "Organizer"); + } + catch (Exception ex) + { + return StatusCode(500, $"Error checking organizer role: {ex.Message}"); + } } - public async Task GetMeetingRoleAsync(string meetingId, string userId, string tenantId) + // Initialize conversation context (optional endpoint for future use) + [Route("InitializeConversation")] + [HttpPost] + public IActionResult InitializeConversation([FromBody] ConversationInitRequest request) { - if (serviceUrl == null) + if (string.IsNullOrEmpty(request?.ConversationId) || string.IsNullOrEmpty(request?.ServiceUrl)) { - throw new InvalidOperationException("Service URL is not avaiable for tenant ID " + tenantId); + return BadRequest(new { error = "Invalid request", message = "ConversationId and ServiceUrl are required" }); } - using var getRoleRequest = new HttpRequestMessage(HttpMethod.Get, new Uri(new Uri(serviceUrl), string.Format("v1/meetings/{0}/participants/{1}?tenantId={2}", meetingId, userId, tenantId))); - getRoleRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await this.botCredentials.GetTokenAsync()); + conversationId = request.ConversationId; + serviceUrl = request.ServiceUrl; - using var getRoleResponse = await this.httpClient.SendAsync(getRoleRequest); + return Ok(new { message = "Conversation initialized successfully" }); + } + + // Check if conversation is initialized + [Route("CheckConversation")] + [HttpGet] + public IActionResult CheckConversation() + { + return Ok(new + { + isInitialized = !string.IsNullOrEmpty(conversationId) && !string.IsNullOrEmpty(serviceUrl), + conversationId = conversationId, + serviceUrl = serviceUrl + }); + } + + // Get Meeting Role for a User + public async Task GetMeetingRoleAsync(string meetingId, string userId, string tenantId) + { + if (string.IsNullOrEmpty(serviceUrl)) + { + throw new InvalidOperationException($"Service URL is not available for tenant ID {tenantId}"); + } + + var httpClient = _httpClientFactory.CreateClient(); + + // Get the bot token from the Teams SDK + var clientId = _configuration["Teams:ClientId"]; + var clientSecret = _configuration["Teams:ClientSecret"]; + + if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) + { + throw new InvalidOperationException("Bot credentials not configured"); + } + + // Get OAuth token for Bot Framework + var token = await GetBotTokenAsync(clientId, clientSecret, tenantId); + + var requestUrl = $"{serviceUrl}v1/meetings/{meetingId}/participants/{userId}?tenantId={tenantId}"; + + using var getRoleRequest = new HttpRequestMessage(HttpMethod.Get, requestUrl); + getRoleRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + using var getRoleResponse = await httpClient.SendAsync(getRoleRequest); getRoleResponse.EnsureSuccessStatusCode(); - var response = JsonConvert.DeserializeObject(await getRoleResponse.Content.ReadAsStringAsync()); + var responseContent = await getRoleResponse.Content.ReadAsStringAsync(); + var response = JsonSerializer.Deserialize(responseContent); + return response; } + + private async Task GetBotTokenAsync(string clientId, string clientSecret, string tenantId) + { + var httpClient = _httpClientFactory.CreateClient(); + + var tokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token"; + + var tokenRequest = new Dictionary + { + { "grant_type", "client_credentials" }, + { "client_id", clientId }, + { "client_secret", clientSecret }, + { "scope", "https://api.botframework.com/.default" } + }; + + var response = await httpClient.PostAsync(tokenEndpoint, new FormUrlEncodedContent(tokenRequest)); + response.EnsureSuccessStatusCode(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var tokenResponse = JsonSerializer.Deserialize>(responseContent); + + return tokenResponse?["access_token"].GetString() ?? throw new InvalidOperationException("Failed to obtain access token"); + } + } + + // Request model for conversation initialization + public class ConversationInitRequest + { + public string? ConversationId { get; set; } + public string? ServiceUrl { get; set; } } -} \ No newline at end of file +} diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Model/TaskInfo.cs b/samples/meetings-sidepanel/csharp/SidePanel/Model/TaskInfo.cs deleted file mode 100644 index 9520d15844..0000000000 --- a/samples/meetings-sidepanel/csharp/SidePanel/Model/TaskInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SidePanel.Models -{ - public class TaskInfo - { - public string Title { get; set; } - } -} \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Model/UserMeetingRoleServiceResponse.cs b/samples/meetings-sidepanel/csharp/SidePanel/Model/UserMeetingRoleServiceResponse.cs deleted file mode 100644 index 50e1b508c9..0000000000 --- a/samples/meetings-sidepanel/csharp/SidePanel/Model/UserMeetingRoleServiceResponse.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace SidePanel.Models -{ - //Organizer role response model - - public class UserMeetingRoleServiceResponse - { - public User user { get; set; } - public Meeting meeting { get; set; } - public Conversation conversation { get; set; } - } - - public class User - { - public string id { get; set; } - public string aadObjectId { get; set; } - public string name { get; set; } - public string givenName { get; set; } - public string surname { get; set; } - public string email { get; set; } - public string userPrincipalName { get; set; } - public string tenantId { get; set; } - public string userRole { get; set; } - } - - public class Meeting - { - public string role { get; set; } - public bool inMeeting { get; set; } - } - - public class Conversation - { - public bool isGroup { get; set; } - public string id { get; set; } - } - -} \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Models/TaskInfo.cs b/samples/meetings-sidepanel/csharp/SidePanel/Models/TaskInfo.cs new file mode 100644 index 0000000000..57bfd6e6a2 --- /dev/null +++ b/samples/meetings-sidepanel/csharp/SidePanel/Models/TaskInfo.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace SidePanel.Models +{ + public class TaskInfo + { + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + } +} diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Models/UserMeetingRoleServiceResponse.cs b/samples/meetings-sidepanel/csharp/SidePanel/Models/UserMeetingRoleServiceResponse.cs new file mode 100644 index 0000000000..2e9ddc8a64 --- /dev/null +++ b/samples/meetings-sidepanel/csharp/SidePanel/Models/UserMeetingRoleServiceResponse.cs @@ -0,0 +1,35 @@ +namespace SidePanel.Models +{ + // Organizer role response model + public class UserMeetingRoleServiceResponse + { + public User? user { get; set; } + public Meeting? meeting { get; set; } + public Conversation? conversation { get; set; } + } + + public class User + { + public string id { get; set; } = string.Empty; + public string aadObjectId { get; set; } = string.Empty; + public string name { get; set; } = string.Empty; + public string givenName { get; set; } = string.Empty; + public string surname { get; set; } = string.Empty; + public string email { get; set; } = string.Empty; + public string userPrincipalName { get; set; } = string.Empty; + public string tenantId { get; set; } = string.Empty; + public string userRole { get; set; } = string.Empty; + } + + public class Meeting + { + public string role { get; set; } = string.Empty; + public bool inMeeting { get; set; } + } + + public class Conversation + { + public bool isGroup { get; set; } + public string id { get; set; } = string.Empty; + } +} diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Program.cs b/samples/meetings-sidepanel/csharp/SidePanel/Program.cs index 9c41918da9..bf50084a07 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/Program.cs +++ b/samples/meetings-sidepanel/csharp/SidePanel/Program.cs @@ -1,25 +1,95 @@ -// 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 Azure.Core; +using Azure.Identity; +using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; +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 SidePanel; +using SidePanel.Controllers; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); +var config = builder.Configuration.Get(); -namespace MeetingApp +if (config == null) { - public class Program + throw new InvalidOperationException("Configuration is not properly set up."); +} + +Func> createTokenFactory = async (string[] scopes, string? tenantId) => +{ + 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 HttpClientFactory for making HTTP requests +builder.Services.AddHttpClient(); + +// Register controllers +builder.Services.AddControllers(); + +// Register the Controller as a singleton +builder.Services.AddSingleton(); + +// In production, the React files will be served from this directory +builder.Services.AddSpaStaticFiles(configuration => +{ + configuration.RootPath = "ClientApp/build"; +}); + +// Add Teams SDK - this automatically registers App as a service +builder.AddTeams(appBuilder); + +var app = builder.Build(); + +// Configure the HTTP request pipeline +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); } + +app.UseDefaultFiles(); +app.UseStaticFiles(); +app.UseSpaStaticFiles(); + +// Enable routing and controllers +app.UseRouting(); +app.MapControllers(); + +app.UseTeams(); + +app.UseSpa(spa => +{ + spa.Options.SourcePath = "ClientApp"; + + if (app.Environment.IsDevelopment()) + { + spa.UseProxyToSpaDevelopmentServer("http://localhost:3000"); + spa.UseReactDevelopmentServer(npmScript: "start"); + } +}); + +app.Run(); \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Properties/launchSettings.json b/samples/meetings-sidepanel/csharp/SidePanel/Properties/launchSettings.json index ff9d8fe153..3572a7a03f 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/Properties/launchSettings.json +++ b/samples/meetings-sidepanel/csharp/SidePanel/Properties/launchSettings.json @@ -1,13 +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": "https://localhost:7130;http://localhost:5130", + "applicationUrl": "http://localhost:5130", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "hotReloadProfile": "aspnetcore" - } + }, } } \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/SidePanel.csproj b/samples/meetings-sidepanel/csharp/SidePanel/SidePanel.csproj index 5ad47531cb..e48430b788 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/SidePanel.csproj +++ b/samples/meetings-sidepanel/csharp/SidePanel/SidePanel.csproj @@ -1,31 +1,32 @@ - + - net6.0 - latest - 82c81116-057f-40bf-9bf2-2563189614e8 + net10.0 + enable - - - - - - - - + + + + + + + + - - - Always + + + + + PreserveNewest + None + + + + PreserveNewest + None - - - - - - - + \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/Startup.cs b/samples/meetings-sidepanel/csharp/SidePanel/Startup.cs deleted file mode 100644 index f9b65fb6a9..0000000000 --- a/samples/meetings-sidepanel/csharp/SidePanel/Startup.cs +++ /dev/null @@ -1,109 +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.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Schema; -using Microsoft.IdentityModel.Tokens; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.BotBuilderSamples; -using Microsoft.BotBuilderSamples.Bots; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using System.Collections.Concurrent; - -namespace MeetingApp -{ - 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.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => - { - options.Authority = "https://login.microsoftonline.com/common"; - options.Audience = this.Configuration["AzureAd:ApplicationId"]; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = false, - }; - }); - - services.AddSingleton( - m => new MicrosoftAppCredentials(this.Configuration["MicrosoftAppId"], this.Configuration["MicrosoftAppPassword"])); - services.AddHttpClient(); - - services.AddControllers().AddNewtonsoftJson(); - - services.AddHttpClient("WebClient", client => client.Timeout = TimeSpan.FromSeconds(600)); - - services.AddHttpContextAccessor(); - - // Create the Bot Framework Adapter with error handling enabled. - services.AddSingleton(); - - // Create a global hashset for our ConversationReferences - services.AddSingleton>(); - - // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. - services.AddTransient(); - - services.AddMvc(options => options.EnableEndpointRouting = false); - - // Storage we'll be using for User and Conversation state. - services.AddSingleton(); - - // Create the Conversation state. - services.AddSingleton(); - - // In production, the React files will be served from this directory - services.AddSpaStaticFiles(configuration => - { - configuration.RootPath = "ClientApp/build"; - }); - } - - // 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.UseSpaStaticFiles(); - app.UseWebSockets(); - app.UseRouting(); - app.UseAuthorization(); - app.UseMvc(); - app.UseEndpoints(endpointRouteBuilder => endpointRouteBuilder.MapControllers()); - - app.UseSpa(spa => - { - spa.Options.SourcePath = "ClientApp"; - - if (env.IsDevelopment()) - { - spa.UseReactDevelopmentServer(npmScript: "start"); - } - }); - } - } -} diff --git a/samples/meetings-sidepanel/csharp/SidePanel/appsettings.Development.json b/samples/meetings-sidepanel/csharp/SidePanel/appsettings.Development.json index b49abfc201..f225c8442d 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/appsettings.Development.json +++ b/samples/meetings-sidepanel/csharp/SidePanel/appsettings.Development.json @@ -1,9 +1,19 @@ { - "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": "" + } +} \ No newline at end of file diff --git a/samples/meetings-sidepanel/csharp/SidePanel/appsettings.json b/samples/meetings-sidepanel/csharp/SidePanel/appsettings.json index 1474d0ecba..aef9dff677 100644 --- a/samples/meetings-sidepanel/csharp/SidePanel/appsettings.json +++ b/samples/meetings-sidepanel/csharp/SidePanel/appsettings.json @@ -1,5 +1,19 @@ { - "MicrosoftAppId": "<>", - "MicrosoftAppPassword": "<>", - "BaseUrl": "<>" + "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/meetings-sidepanel/csharp/SidePanel/wwwroot/config.html b/samples/meetings-sidepanel/csharp/SidePanel/wwwroot/config.html new file mode 100644 index 0000000000..d30c4b5ccf --- /dev/null +++ b/samples/meetings-sidepanel/csharp/SidePanel/wwwroot/config.html @@ -0,0 +1,195 @@ + + + + + + Side Panel + + + + + About + ? + + + + ? + Side Panel + + + Welcome to Contoso Media! + Press the save button to continue. + + Save + + + + + diff --git a/samples/meetings-sidepanel/csharp/SidePanel/wwwroot/default.htm b/samples/meetings-sidepanel/csharp/SidePanel/wwwroot/default.htm deleted file mode 100644 index dbb63b7b76..0000000000 --- a/samples/meetings-sidepanel/csharp/SidePanel/wwwroot/default.htm +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - - MeetingSidePanelApp - - - - - - - - - Meeting Sidepanel App - - - - - Your bot is ready! - You can test your bot in the Bot Framework Emulator - by connecting to http://localhost:3001/api/messages. - Download the Emulator - - 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/meetings-sidepanel/csharp/SidePanel/wwwroot/sidepanel.html b/samples/meetings-sidepanel/csharp/SidePanel/wwwroot/sidepanel.html new file mode 100644 index 0000000000..dfcebd02f4 --- /dev/null +++ b/samples/meetings-sidepanel/csharp/SidePanel/wwwroot/sidepanel.html @@ -0,0 +1,403 @@ + + + + + + Live Coding + + + + + + + Live Coding + + + ? + ? + ? + + + + + View + Notes + Live Coding + Apps + More + Camera + Mic + Share + + + + + + Agenda + + + Add New Agenda Item + + + + + + Publish Agenda + + + + + diff --git a/samples/msgext-thirdparty-storage/csharp/AdapterWithErrorHandler.cs b/samples/msgext-thirdparty-storage/csharp/AdapterWithErrorHandler.cs deleted file mode 100644 index 049d8ac514..0000000000 --- a/samples/msgext-thirdparty-storage/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, $"[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 turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); - }; - } - } -} diff --git a/samples/msgext-thirdparty-storage/csharp/Bots/TeamsMsgextThirdpartyStorageBot.cs b/samples/msgext-thirdparty-storage/csharp/Bots/TeamsMsgextThirdpartyStorageBot.cs deleted file mode 100644 index a7d8c9c9fd..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/Bots/TeamsMsgextThirdpartyStorageBot.cs +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AdaptiveCards; -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 Microsoft.BotBuilderSamples.Bots -{ - /// - /// A bot that handles Teams messaging extensions for third-party storage integration. - /// - public class TeamsMsgextThirdpartyStorageBot : TeamsActivityHandler - { - private readonly string baseUrl; - - /// - /// Initializes a new instance of the class. - /// - /// Configuration object to access app settings. - public TeamsMsgextThirdpartyStorageBot(IConfiguration configuration) - { - this.baseUrl = configuration["BaseUrl"] ?? throw new ArgumentNullException(nameof(configuration), "BaseUrl is not configured."); - } - - /// - /// Handles the submit action for messaging extensions. - /// - /// The turn context. - /// The messaging extension action. - /// The cancellation token. - /// A task that represents the messaging extension response. - protected override async Task OnTeamsMessagingExtensionSubmitActionAsync( - ITurnContext turnContext, - MessagingExtensionAction action, - CancellationToken cancellationToken) - { - try - { - switch (action.CommandId) - { - case "createWithPreview": - return CreateWebViewResponse(turnContext, action); - default: - throw new NotSupportedException($"Command '{action.CommandId}' is not supported."); - } - } - catch (Exception ex) - { - // Log the exception - Console.WriteLine($"Error in OnTeamsMessagingExtensionSubmitActionAsync: {ex.Message}"); - return new MessagingExtensionActionResponse - { - ComposeExtension = new MessagingExtensionResult - { - Type = "message", - Text = "An error occurred while processing your request. Please try again later." - } - }; - } - } - - /// - /// Generates a messaging extension action response containing an Adaptive Card with file details. - /// - /// The turn context. - /// The messaging extension action. - /// A MessagingExtensionActionResponse with an Adaptive Card. - private MessagingExtensionActionResponse CreateWebViewResponse(ITurnContext turnContext, MessagingExtensionAction action) - { - try - { - // Parse the uploaded data from action.Data - var uploadedFiles = JArray.Parse(action.Data.ToString()); - - // Dictionary for file type icons - var fileTypeIcons = new Dictionary - { - { "spreadsheet", $"{this.baseUrl}/icons/ExcelIcon.png" }, - { "pdf", $"{this.baseUrl}/icons/PdfIcon.png" }, - { "wordprocessing", $"{this.baseUrl}/icons/WordIcons.png" }, - { "image", $"{this.baseUrl}/icons/ImageIcon.png" }, - }; - - var cardBody = new List(); - - foreach (var file in uploadedFiles) - { - string name = file["name"]?.ToString() ?? "Unknown"; - string type = file["type"]?.ToString() ?? string.Empty; - string fileTypeIconUrl = fileTypeIcons.FirstOrDefault(kvp => type.Contains(kvp.Key)).Value - ?? $"{this.baseUrl}/icons/DefaultIcon.png"; - - cardBody.Add(new AdaptiveColumnSet - { - Columns = new List - { - new AdaptiveColumn - { - Width = "auto", - Items = new List - { - new AdaptiveImage - { - Url = new Uri(fileTypeIconUrl), - Size = AdaptiveImageSize.Small - } - } - }, - new AdaptiveColumn - { - Width = "stretch", - Items = new List - { - new AdaptiveTextBlock - { - Text = name, - Wrap = true - } - } - } - } - }); - } - - // Create Adaptive Card - var adaptiveCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 4)) { Body = cardBody }; - - return new MessagingExtensionActionResponse - { - ComposeExtension = new MessagingExtensionResult - { - Type = "result", - AttachmentLayout = "list", - Attachments = new List - { - new MessagingExtensionAttachment - { - ContentType = AdaptiveCard.ContentType, - Content = adaptiveCard - } - } - } - }; - } - catch (Exception ex) - { - // Log the exception - Console.WriteLine($"Error in CreateWebViewResponse: {ex.Message}"); - throw; - } - } - - /// - /// Handles the fetch task for messaging extensions. - /// - /// The turn context. - /// The messaging extension action. - /// The cancellation token. - /// A task that represents the messaging extension response. - protected override async Task OnTeamsMessagingExtensionFetchTaskAsync( - ITurnContext turnContext, - MessagingExtensionAction action, - CancellationToken cancellationToken) - { - try - { - // Check if the replyToId is empty and commandContext is "thirdParty" - var activityValue = turnContext.Activity.Value as JObject; - var commandContext = activityValue?["commandContext"]?.ToString(); - var replyToId = activityValue?["messagePayload"]?["replyToId"]?.ToString(); - - // Process if conditions are met - if (replyToId == "" && commandContext == "thirdParty") - { - // Call the method to generate the response based on context - return CreateMediaNameDetailsTaskResponse(turnContext, action); - } - - // Default response for other conditions - return new MessagingExtensionActionResponse - { - Task = new TaskModuleContinueResponse - { - Value = new TaskModuleTaskInfo - { - Title = "Default Task", - Height = 200, - Width = 400, - Url = null - } - } - }; - } - catch (Exception ex) - { - // Log the exception for debugging - Console.WriteLine($"Error in OnTeamsMessagingExtensionFetchTaskAsync: {ex.Message}"); - - // Return an error response - return new MessagingExtensionActionResponse - { - Task = new TaskModuleContinueResponse - { - Value = new TaskModuleTaskInfo - { - Title = "Error", - Height = 200, - Width = 400, - Url = null - } - } - }; - } - } - - /// - /// Generates a task module response for the employee details form. - /// - /// The turn context. - /// The messaging extension action. - /// A MessagingExtensionActionResponse with a task module. - private MessagingExtensionActionResponse CreateMediaNameDetailsTaskResponse(ITurnContext turnContext, MessagingExtensionAction action) - { - try - { - return new MessagingExtensionActionResponse - { - Task = new TaskModuleContinueResponse - { - Value = new TaskModuleTaskInfo - { - Height = 530, - Width = 700, - Title = "Task Module WebView", - Url = $"{this.baseUrl}/CustomForm", - }, - }, - }; - } - catch (Exception ex) - { - // Log the exception - Console.WriteLine($"Error in CreateMediaNameDetailsTaskResponse: {ex.Message}"); - throw; - } - } - } -} diff --git a/samples/msgext-thirdparty-storage/csharp/Controllers/BotController.cs b/samples/msgext-thirdparty-storage/csharp/Controllers/BotController.cs deleted file mode 100644 index f05d813596..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/Controllers/BotController.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.AspNetCore.Mvc; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; - -namespace Microsoft.BotBuilderSamples.Controllers -{ - /// - /// ASP.NET Controller for processing bot messages. - /// Handles HTTP POST requests to the bot and delegates processing to the Bot Framework adapter. - /// - [Route("api/messages")] - [ApiController] - public class BotController : ControllerBase - { - private readonly CloudAdapter Adapter; - private readonly IBot Bot; - - /// - /// Initializes a new instance of the class. - /// Dependency injection provides the adapter and bot implementation at runtime. - /// - /// The Bot Framework adapter. - /// The bot implementation. - public BotController(CloudAdapter adapter, IBot bot) - { - Adapter = adapter; - Bot = bot; - } - - /// - /// Handles HTTP POST requests and delegates them to the Bot Framework adapter. - /// The adapter processes the request and invokes the bot's logic. - /// - /// A task representing the asynchronous operation. - [HttpPost] - public async Task PostAsync() - { - try - { - await Adapter.ProcessAsync(Request, Response, Bot); - } - catch (Exception ex) - { - // Log or handle errors here - Console.Error.WriteLine($"Error processing request: {ex.Message}"); - throw; - } - } - } -} diff --git a/samples/msgext-thirdparty-storage/csharp/Controllers/HomeController.cs b/samples/msgext-thirdparty-storage/csharp/Controllers/HomeController.cs deleted file mode 100644 index 1bc4ad03f2..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/Controllers/HomeController.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -using Microsoft.AspNetCore.Mvc; - -/// -/// The HomeController handles navigation to various views. -/// -public class HomeController : Controller -{ - /// - /// Returns the CustomForm view. - /// - /// An ActionResult object for the CustomForm view. - [Route("/CustomForm")] - public ActionResult CustomForm() - { - return View("CustomForm"); - } -} diff --git a/samples/msgext-thirdparty-storage/csharp/M365Agent/.gitignore b/samples/msgext-thirdparty-storage/csharp/M365Agent/.gitignore new file mode 100644 index 0000000000..c5cae9258c --- /dev/null +++ b/samples/msgext-thirdparty-storage/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-thirdparty-storage/csharp/M365Agent/M365Agent.atkproj b/samples/msgext-thirdparty-storage/csharp/M365Agent/M365Agent.atkproj new file mode 100644 index 0000000000..124eb75046 --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/M365Agent/M365Agent.atkproj @@ -0,0 +1,9 @@ + + + + b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf + + + + + \ No newline at end of file diff --git a/samples/msgext-thirdparty-storage/csharp/M365Agent/M365Agent.ttkproj b/samples/msgext-thirdparty-storage/csharp/M365Agent/M365Agent.ttkproj deleted file mode 100644 index 79e4ce2efd..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/M365Agent/M365Agent.ttkproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - 95754b61-0437-49e0-a664-9564f36e58fb - - - - - - - - - - \ No newline at end of file diff --git a/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/color.png b/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/color.png index b8cf81afbe..01aa37e347 100644 Binary files a/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/color.png and b/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/color.png differ diff --git a/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/manifest.json b/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/manifest.json index b512c550fe..7d00405ac8 100644 --- a/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/manifest.json +++ b/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/manifest.json @@ -24,7 +24,7 @@ "accentColor": "#FFFFFF", "bots": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "${{BOT_ID}}", "needsChannelSelector": false, "isNotificationOnly": false, "supportsCalling": false, @@ -39,7 +39,7 @@ ], "composeExtensions": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "${{BOT_ID}}", "canUpdateConfiguration": false, "commands": [ { diff --git a/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/outline.png b/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/outline.png index 2c3bf6fa65..f7a4c86447 100644 Binary files a/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/outline.png and b/samples/msgext-thirdparty-storage/csharp/M365Agent/appPackage/outline.png differ diff --git a/samples/msgext-thirdparty-storage/csharp/M365Agent/env/.env.dev b/samples/msgext-thirdparty-storage/csharp/M365Agent/env/.env.dev new file mode 100644 index 0000000000..df4f9da508 --- /dev/null +++ b/samples/msgext-thirdparty-storage/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-thirdparty-storage/csharp/M365Agent/env/.env.local b/samples/msgext-thirdparty-storage/csharp/M365Agent/env/.env.local index 76ee8a0ba6..86dfacc35a 100644 --- a/samples/msgext-thirdparty-storage/csharp/M365Agent/env/.env.local +++ b/samples/msgext-thirdparty-storage/csharp/M365Agent/env/.env.local @@ -7,17 +7,8 @@ APP_NAME_SUFFIX=local # Generated during provision, you can also add your own variables. BOT_ID= TEAMS_APP_ID= -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= +BOT_OBJECT_ID= TEAMSFX_M365_USER_NAME= BOT_ENDPOINT= diff --git a/samples/msgext-thirdparty-storage/csharp/M365Agent/infra/azure.bicep b/samples/msgext-thirdparty-storage/csharp/M365Agent/infra/azure.bicep index c3ce051b3d..658e412a21 100644 --- a/samples/msgext-thirdparty-storage/csharp/M365Agent/infra/azure.bicep +++ b/samples/msgext-thirdparty-storage/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-thirdparty-storage/csharp/M365Agent/infra/azure.parameters.json b/samples/msgext-thirdparty-storage/csharp/M365Agent/infra/azure.parameters.json index 96959422b9..40d90c245f 100644 --- a/samples/msgext-thirdparty-storage/csharp/M365Agent/infra/azure.parameters.json +++ b/samples/msgext-thirdparty-storage/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-thirdparty-storage" - }, - "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": "TeamsMsgextThirdpartyStorage" + } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/samples/msgext-thirdparty-storage/csharp/M365Agent/infra/botRegistration/azurebot.bicep b/samples/msgext-thirdparty-storage/csharp/M365Agent/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..a5a27b8fe4 --- /dev/null +++ b/samples/msgext-thirdparty-storage/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-thirdparty-storage/csharp/M365Agent/infra/botRegistration/readme.md b/samples/msgext-thirdparty-storage/csharp/M365Agent/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/msgext-thirdparty-storage/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-thirdparty-storage/csharp/M365Agent/launchSettings.json b/samples/msgext-thirdparty-storage/csharp/M365Agent/launchSettings.json index 8c76c70d9e..2af8ce7a8a 100644 --- a/samples/msgext-thirdparty-storage/csharp/M365Agent/launchSettings.json +++ b/samples/msgext-thirdparty-storage/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-thirdparty-storage/csharp/M365Agent/m365agents.local.yml b/samples/msgext-thirdparty-storage/csharp/M365Agent/m365agents.local.yml index 1e84af1ce3..2fa0fd6f50 100644 --- a/samples/msgext-thirdparty-storage/csharp/M365Agent/m365agents.local.yml +++ b/samples/msgext-thirdparty-storage/csharp/M365Agent/m365agents.local.yml @@ -1,61 +1,55 @@ -# 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-thirdparty-storage-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-thirdparty-storage-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-thirdparty-storage${{APP_NAME_SUFFIX}} + name: TeamsMsgextThirdpartyStorage${{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: TeamsMsgextThirdpartyStorage${{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: ../TeamsMsgextThirdpartyStorage/appsettings.Development.json content: - MicrosoftAppId: ${{AAD_APP_CLIENT_ID}} - MicrosoftAppPassword: ${{SECRET_AAD_APP_CLIENT_SECRET}} - MicrosoftAppType: ${{MICROSOFT_APP_TYPE}} - MicrosoftAppTenantId: ${{MICROSOFT_APP_TENANT_ID}} - BaseUrl: ${{BOT_ENDPOINT}} + Teams: + ClientId: ${{BOT_ID}} + ClientSecret: ${{SECRET_BOT_PASSWORD}} + TenantId: ${{TEAMS_APP_TENANT_ID}} + BaseUrl: ${{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. + botId: ${{BOT_ID}} + name: TeamsMsgextThirdpartyStorage + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams # manifest file to determine which AAD app to update. - uses: aadApp/update @@ -70,14 +64,13 @@ provision: 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 +83,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-thirdparty-storage/csharp/M365Agent/m365agents.yml b/samples/msgext-thirdparty-storage/csharp/M365Agent/m365agents.yml index 6b6e7d9127..c2550ac491 100644 --- a/samples/msgext-thirdparty-storage/csharp/M365Agent/m365agents.yml +++ b/samples/msgext-thirdparty-storage/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-thirdparty-storage-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: TeamsMsgextThirdpartyStorage${{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 + TeamsMsgextThirdpartyStorage.csproj + workingDirectory: ../TeamsMsgextThirdpartyStorage + # 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: ../TeamsMsgextThirdpartyStorage +projectId: 7660d68b-f2b7-4721-8c1f-17d215a0eab1 diff --git a/samples/msgext-thirdparty-storage/csharp/Program.cs b/samples/msgext-thirdparty-storage/csharp/Program.cs deleted file mode 100644 index 15de094baf..0000000000 --- a/samples/msgext-thirdparty-storage/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/msgext-thirdparty-storage/csharp/Properties/launchSettings.json b/samples/msgext-thirdparty-storage/csharp/Properties/launchSettings.json deleted file mode 100644 index ff9d8fe153..0000000000 --- a/samples/msgext-thirdparty-storage/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/msgext-thirdparty-storage/csharp/README.md b/samples/msgext-thirdparty-storage/csharp/README.md index e6ed918217..79517662b8 100644 --- a/samples/msgext-thirdparty-storage/csharp/README.md +++ b/samples/msgext-thirdparty-storage/csharp/README.md @@ -102,22 +102,20 @@ the Teams service needs to call into the bot. - Navigate to `samples/msgext-thirdparty-storage/csharp` folder - Select `TeamsMsgextThirdpartyStorage.csproj` or `TeamsMsgextThirdpartyStorage.sln`file -1) Update the `appsettings.json` configuration for the bot to use the MicrosoftAppId, MicrosoftAppPassword, MicrosoftAppTenantId generated in Step 1 (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.) - - Set "MicrosoftAppType" in the `appsettings.json`. (**Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI**) +1) Update the `appsettings.Development.json` configuration for the bot to use the ClientId, ClientSecret, TenantId generated in Step 1 (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.) + - Set "MicrosoftAppType" in the `appsettings.Development.json`. (**Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI**) - - Set "BaseUrl" in the `appsettings.json` as per your application like the ngrok forwarding url (ie `https://xxxx.ngrok-free.app`) after starting ngrok and if you are using dev tunnels, your URL will be like: https://12345.devtunnels.ms. + - Set "BaseUrl" in the `appsettings.Development.json` as per your application like the ngrok forwarding url (ie `https://xxxx.ngrok-free.app`) after starting ngrok and if you are using dev tunnels, your URL will be like: https://12345.devtunnels.ms. 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 `BOT_ID` and `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` 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 `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 to your org's app catalog". Browse to and Open the .zip file. At the next dialog, click the Add button.) - Add the bot 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/msgext-thirdparty-storage/csharp/AdapterWithErrorHandler.cs#L25) line and put your debugger for local debug. - ## Running the sample **Upload an Application to Teams** diff --git a/samples/msgext-thirdparty-storage/csharp/Startup.cs b/samples/msgext-thirdparty-storage/csharp/Startup.cs deleted file mode 100644 index aeb36ba5dd..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/Startup.cs +++ /dev/null @@ -1,71 +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; - -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.AddControllers(); - services.AddHttpClient(); - services.AddMvc(); - services.AddControllers().AddNewtonsoftJson(options => - { - options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth; - }); - services.AddRazorPages(); - - // Create the Bot Framework Authentication to be used with the Bot Adapter. - 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(); - app.UseStaticFiles(); - - // Runs matching. An endpoint is selected and set on the HttpContext if a match is found. - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapRazorPages(); - // Mapping of endpoints goes here: - endpoints.MapControllers(); - }); - - } - } -} diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.csproj b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.csproj deleted file mode 100644 index 996d0660fb..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net6.0 - latest - - - - - - - - - - - - - - - - - - Always - - - - - - diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.sln b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.sln deleted file mode 100644 index 06c9352e1a..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.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}") = "TeamsMsgextThirdpartyStorage", "TeamsMsgextThirdpartyStorage.csproj", "{C184FC67-19AD-43DA-9553-6A2B10E820BE}" -EndProject -Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{95754B61-0437-49E0-A664-9564F36E58FB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CAEA9E49-DFC1-4705-8CE2-DE8A4E7ECFA3}" - ProjectSection(SolutionItems) = preProject - TeamsMsgextThirdpartyStorage.slnLaunch.user = TeamsMsgextThirdpartyStorage.slnLaunch.user - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C184FC67-19AD-43DA-9553-6A2B10E820BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C184FC67-19AD-43DA-9553-6A2B10E820BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C184FC67-19AD-43DA-9553-6A2B10E820BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C184FC67-19AD-43DA-9553-6A2B10E820BE}.Release|Any CPU.Build.0 = Release|Any CPU - {95754B61-0437-49E0-A664-9564F36E58FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {95754B61-0437-49E0-A664-9564F36E58FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {95754B61-0437-49E0-A664-9564F36E58FB}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {95754B61-0437-49E0-A664-9564F36E58FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {95754B61-0437-49E0-A664-9564F36E58FB}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3A3F5CEA-0632-46A1-BBEC-F604F8BB524F} - EndGlobalSection -EndGlobal diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.slnLaunch.user b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.slnLaunch.user index cf7c2fa64d..aa79882b87 100644 --- a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.slnLaunch.user +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.slnLaunch.user @@ -1,31 +1,52 @@ [ { - "Name": "Microsoft Teams (browser)", + "Name": "Microsoft 365 Agents Playground (browser)", "Projects": [ { - "Path": "TeamsMsgextThirdpartyStorage.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": "TeamsMsgextThirdpartyStorage\\TeamsMsgextThirdpartyStorage.csproj", + "Name": "TeamsMsgextThirdpartyStorage\\TeamsMsgextThirdpartyStorage.csproj", + "Action": "Start", + "DebugTarget": "Microsoft 365 Agents Playground" } ] }, { - "Name": "Microsoft Teams (browser) (skip update app)", + "Name": "Microsoft Teams (browser)", "Projects": [ { - "Path": "TeamsMsgextThirdpartyStorage.csproj", + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft Teams (browser)" + }, + { + "Path": "TeamsMsgextThirdpartyStorage\\TeamsMsgextThirdpartyStorage.csproj", + "Name": "TeamsMsgextThirdpartyStorage\\TeamsMsgextThirdpartyStorage.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": "TeamsMsgextThirdpartyStorage\\TeamsMsgextThirdpartyStorage.csproj", + "Name": "TeamsMsgextThirdpartyStorage\\TeamsMsgextThirdpartyStorage.csproj", + "Action": "Start", + "DebugTarget": "Start Project" } ] } diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.slnx b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.slnx new file mode 100644 index 0000000000..53260ad57e --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/samples/msgext-thirdparty-storage/csharp/.gitignore b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/.gitignore similarity index 70% rename from samples/msgext-thirdparty-storage/csharp/.gitignore rename to samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/.gitignore index 7466c01f9c..77c7154916 100644 --- a/samples/msgext-thirdparty-storage/csharp/.gitignore +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/.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-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Config.cs b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Config.cs new file mode 100644 index 0000000000..67ff6e5332 --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Config.cs @@ -0,0 +1,16 @@ +namespace TeamsMsgextThirdpartyStorage +{ + public class ConfigOptions + { + public TeamsConfigOptions Teams { 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 string BaseUrl { get; set; } + } +} \ No newline at end of file diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Controllers/Controller.cs b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Controllers/Controller.cs new file mode 100644 index 0000000000..13e4fbaba9 --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Controllers/Controller.cs @@ -0,0 +1,275 @@ +using Microsoft.Teams.Api.MessageExtensions; +using Microsoft.Teams.Api.TaskModules; +using Microsoft.Teams.Apps.Annotations; +using System.Text.Json; +using Microsoft.Teams.Apps.Activities.Invokes; +using Microsoft.Teams.Common; +using MessageExtensionResponse = Microsoft.Teams.Api.MessageExtensions.Response; +using TaskModuleSize = Microsoft.Teams.Api.TaskModules.Size; + + +namespace TeamsMsgextThirdpartyStorage.Controllers +{ + [TeamsController] + public class Controller + { + private readonly ConfigOptions _config; + + public Controller(IConfiguration configuration) + { + _config = configuration.Get() ?? throw new NullReferenceException("ConfigOptions"); + } + + /// + /// Handles messaging extension fetch task - displays the custom form for file upload + /// + [Invoke("composeExtension/fetchTask")] + public async Task OnFetchTask( + [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.FetchTaskActivity activity, + [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + log.Info("FetchTask invoked for third-party storage"); + + try + { + var commandContext = activity.Value?.CommandContext; + var commandId = activity.Value?.CommandId; + + log.Info($"Command Context: {commandContext}"); + log.Info($"Command ID: {commandId}"); + + // Always return the task module for file upload + // The thirdParty context will be set when files are dragged and dropped from third-party storage + return new ActionResponse + { + Task = new ContinueTask(new TaskInfo + { + Title = "Third-Party Storage - File Upload", + Height = new Union(530), + Width = new Union(700), + Url = $"{_config.Teams.BaseUrl}/CustomForm" + }) + }; + } + catch (Exception ex) + { + log.Error($"Error in OnFetchTask: {ex.Message}", ex); + throw; + } + } + + /// + /// Handles messaging extension submit action - processes the uploaded files + /// + [Invoke("composeExtension/submitAction")] + public async Task OnSubmitAction( + [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, + [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + log.Info("SubmitAction invoked for third-party storage"); + + try + { + var commandId = activity.Value?.CommandId; + + if (commandId == "createWithPreview") + { + return CreateWebViewResponse(activity, log); + } + + throw new NotSupportedException($"Command '{commandId}' is not supported."); + } + catch (Exception ex) + { + log.Error($"Error in OnSubmitAction: {ex.Message}", ex); + return new MessageExtensionResponse + { + ComposeExtension = new Result + { + Type = ResultType.Message, + Text = "An error occurred while processing your request. Please try again later." + } + }; + } + } + + /// + /// Creates an adaptive card response with file details + /// + private MessageExtensionResponse CreateWebViewResponse( + Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, + Microsoft.Teams.Common.Logging.ILogger log) + { + try + { + // Parse the uploaded data from activity.Value.Data + var data = activity.Value?.Data; + log.Info($"Received data: {data?.ToString() ?? "null"}"); + log.Info($"Data type: {data?.GetType().FullName ?? "null"}"); + + List uploadedFiles = null; + + // Check if data is already a JSON element or needs to be deserialized + if (data is JsonElement jsonElement) + { + log.Info($"Data is JsonElement, ValueKind: {jsonElement.ValueKind}"); + uploadedFiles = JsonSerializer.Deserialize>(jsonElement.GetRawText()); + } + else if (data is string jsonString) + { + log.Info("Data is string"); + uploadedFiles = JsonSerializer.Deserialize>(jsonString); + } + else + { + log.Info("Data is another type, converting to string"); + uploadedFiles = JsonSerializer.Deserialize>(data?.ToString() ?? "[]"); + } + + log.Info($"Deserialized {uploadedFiles?.Count ?? 0} files"); + + var cardElements = new List(); + + foreach (var file in uploadedFiles ?? new List()) + { + string name = file.Name ?? "Unknown"; + string type = file.Type ?? string.Empty; + + log.Info($"Processing file: Name={name}, Type={type}, Size={file.Size}"); + + // Determine the icon based on file extension or MIME type + string fileTypeIconUrl = GetFileIconUrl(name, type); + log.Info($"Selected icon URL: {fileTypeIconUrl}"); + + cardElements.Add(new + { + type = "ColumnSet", + columns = new object[] + { + new + { + type = "Column", + width = "auto", + items = new object[] + { + new + { + type = "Image", + url = fileTypeIconUrl, + size = "Small" + } + } + }, + new + { + type = "Column", + width = "stretch", + items = new object[] + { + new + { + type = "TextBlock", + text = name, + wrap = true + } + } + } + } + }); + } + + // Create Adaptive Card + var adaptiveCard = new + { + type = "AdaptiveCard", + body = cardElements, + version = "1.4" + }; + + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard, + Content = adaptiveCard + }; + + return new MessageExtensionResponse + { + ComposeExtension = new Result + { + Type = ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List { attachment } + } + }; + } + catch (Exception ex) + { + log.Error($"Error in CreateWebViewResponse: {ex.Message}", ex); + throw; + } + } + + /// + /// Determines the appropriate icon URL based on file name extension or MIME type + /// + private string GetFileIconUrl(string fileName, string mimeType) + { + // Get file extension + string extension = Path.GetExtension(fileName).ToLowerInvariant(); + + switch (extension) + { + case ".pdf": + return $"{_config.Teams.BaseUrl}/icons/PdfIcon.png"; + + case ".doc": + case ".docx": + return $"{_config.Teams.BaseUrl}/icons/WordIcons.png"; + + case ".xls": + case ".xlsx": + case ".csv": + return $"{_config.Teams.BaseUrl}/icons/ExcelIcon.png"; + + case ".png": + case ".jpg": + case ".jpeg": + case ".gif": + case ".bmp": + case ".svg": + return $"{_config.Teams.BaseUrl}/icons/ImageIcon.png"; + } + + if (!string.IsNullOrEmpty(mimeType)) + { + string mimeTypeLower = mimeType.ToLowerInvariant(); + + if (mimeTypeLower.Contains("pdf")) + return $"{_config.Teams.BaseUrl}/icons/PdfIcon.png"; + + if (mimeTypeLower.Contains("word") || mimeTypeLower.Contains("document")) + return $"{_config.Teams.BaseUrl}/icons/WordIcons.png"; + + if (mimeTypeLower.Contains("spreadsheet") || mimeTypeLower.Contains("excel")) + return $"{_config.Teams.BaseUrl}/icons/ExcelIcon.png"; + + if (mimeTypeLower.Contains("image")) + return $"{_config.Teams.BaseUrl}/icons/ImageIcon.png"; + } + + return $"{_config.Teams.BaseUrl}/icons/ImageIcon.png"; + } + + private class FileInfo + { + [System.Text.Json.Serialization.JsonPropertyName("name")] + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("type")] + public string Type { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("size")] + public long Size { get; set; } + } + } +} diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Controllers/HomeController.cs b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Controllers/HomeController.cs new file mode 100644 index 0000000000..6dfec54b90 --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Controllers/HomeController.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using Microsoft.AspNetCore.Mvc; + +namespace TeamsMsgextThirdpartyStorage.Controllers +{ + /// + /// The HomeController handles navigation to various views. + /// + public class HomeController : Microsoft.AspNetCore.Mvc.Controller + { + /// + /// Returns the CustomForm view. + /// + /// An ActionResult object for the CustomForm view. + [Route("/CustomForm")] + public ActionResult CustomForm() + { + return View("CustomForm"); + } + } +} diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Middleware/TeamsActivityMiddleware.cs b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Middleware/TeamsActivityMiddleware.cs new file mode 100644 index 0000000000..3974203963 --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Middleware/TeamsActivityMiddleware.cs @@ -0,0 +1,138 @@ +using System.Text; +using System.Text.Json; + +namespace TeamsMsgextThirdpartyStorage.Middleware +{ + public class TeamsActivityMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public TeamsActivityMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context) + { + // Only process POST requests to /api/messages + if (context.Request.Method == "POST" && context.Request.Path.StartsWithSegments("/api/messages")) + { + context.Request.EnableBuffering(); + + using var reader = new StreamReader(context.Request.Body, Encoding.UTF8, leaveOpen: true); + var body = await reader.ReadToEndAsync(); + context.Request.Body.Position = 0; + + try + { + var jsonDoc = JsonDocument.Parse(body); + var root = jsonDoc.RootElement; + + // Check if this is a composeExtension invoke (fetchTask or submitAction) + if (root.TryGetProperty("type", out var typeElement) && + typeElement.GetString() == "invoke" && + root.TryGetProperty("name", out var nameElement)) + { + var invokeName = nameElement.GetString(); + + // Handle both fetchTask and submitAction + if (invokeName == "composeExtension/fetchTask" || invokeName == "composeExtension/submitAction") + { + // Check if there's a messagePayload without an id + if (root.TryGetProperty("value", out var valueElement) && + valueElement.TryGetProperty("messagePayload", out var messagePayloadElement)) + { + // If messagePayload exists but doesn't have an id, add a placeholder + if (!messagePayloadElement.TryGetProperty("id", out _)) + { + _logger.LogInformation($"Adding placeholder id to messagePayload for {invokeName}"); + + // Reconstruct the JSON with a placeholder id + var modifiedJson = AddPlaceholderIdToMessagePayload(body); + + // Replace the request body with the modified JSON + var bytes = Encoding.UTF8.GetBytes(modifiedJson); + context.Request.Body = new MemoryStream(bytes); + context.Request.ContentLength = bytes.Length; + } + } + } + } + } + catch (JsonException ex) + { + _logger.LogWarning($"Failed to parse JSON body: {ex.Message}"); + } + } + + await _next(context); + } + + private string AddPlaceholderIdToMessagePayload(string json) + { + try + { + using var jsonDoc = JsonDocument.Parse(json); + var root = jsonDoc.RootElement; + + // Create a new JSON object with the id added to messagePayload + var options = new JsonWriterOptions { Indented = false }; + using var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream, options)) + { + writer.WriteStartObject(); + + foreach (var property in root.EnumerateObject()) + { + if (property.Name == "value" && property.Value.ValueKind == JsonValueKind.Object) + { + writer.WritePropertyName("value"); + writer.WriteStartObject(); + + foreach (var valueProperty in property.Value.EnumerateObject()) + { + if (valueProperty.Name == "messagePayload" && valueProperty.Value.ValueKind == JsonValueKind.Object) + { + writer.WritePropertyName("messagePayload"); + writer.WriteStartObject(); + + // Add placeholder id first + writer.WriteString("id", "placeholder-message-id"); + + // Copy all existing properties + foreach (var msgProperty in valueProperty.Value.EnumerateObject()) + { + msgProperty.WriteTo(writer); + } + + writer.WriteEndObject(); + } + else + { + valueProperty.WriteTo(writer); + } + } + + writer.WriteEndObject(); + } + else + { + property.WriteTo(writer); + } + } + + writer.WriteEndObject(); + } + + return Encoding.UTF8.GetString(stream.ToArray()); + } + catch (Exception ex) + { + // If modification fails, return original JSON + return json; + } + } + } +} diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Program.cs b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Program.cs new file mode 100644 index 0000000000..56aee4eeab --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Program.cs @@ -0,0 +1,76 @@ +using TeamsMsgextThirdpartyStorage; +using TeamsMsgextThirdpartyStorage.Controllers; +using TeamsMsgextThirdpartyStorage.Middleware; +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.Text.Json; +using System.Text.Json.Serialization; + +var builder = WebApplication.CreateBuilder(args); +var config = builder.Configuration.Get(); + +// Configure JSON serialization options to handle missing required properties +builder.Services.Configure(options => +{ + options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.PropertyNameCaseInsensitive = true; +}); + +Func> createTokenFactory = async (string[] scopes, string? tenantId) => +{ + 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 + { + 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) => + { + return await createTokenFactory(scopes, tenantId); + } + )); +} + +// Add support for MVC and Razor views +builder.Services.AddControllersWithViews(); +builder.Services.AddRazorPages(); + +builder.Services.AddSingleton(); +builder.AddTeams(appBuilder); + +var app = builder.Build(); + +// Add custom middleware to handle Teams activity payloads BEFORE the Teams middleware +app.UseMiddleware(); + +// Enable static files (CSS, JS, images, etc.) +app.UseStaticFiles(); + +// Enable routing +app.UseRouting(); + +// Map controller routes and Razor pages +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); +app.MapRazorPages(); + +app.UseTeams(); +app.Run(); diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Properties/launchSettings.json b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Properties/launchSettings.json new file mode 100644 index 0000000000..3572a7a03f --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/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/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/TeamsMsgextThirdpartyStorage.csproj b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/TeamsMsgextThirdpartyStorage.csproj new file mode 100644 index 0000000000..d7886df2e7 --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/TeamsMsgextThirdpartyStorage.csproj @@ -0,0 +1,29 @@ + + + + net10.0 + enable + + + + + + + + + + + + + + + PreserveNewest + None + + + + PreserveNewest + None + + + \ No newline at end of file diff --git a/samples/msgext-thirdparty-storage/csharp/Views/Home/CustomForm.cshtml b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Views/Home/CustomForm.cshtml similarity index 98% rename from samples/msgext-thirdparty-storage/csharp/Views/Home/CustomForm.cshtml rename to samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Views/Home/CustomForm.cshtml index abfdca2d11..6634d3847e 100644 --- a/samples/msgext-thirdparty-storage/csharp/Views/Home/CustomForm.cshtml +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Views/Home/CustomForm.cshtml @@ -1,4 +1,4 @@ -@{ +@{ ViewData["Title"] = "CustomForm"; Layout = "~/Views/Shared/_Layout.cshtml"; } @@ -100,7 +100,7 @@ if (progress >= 100) { clearInterval(interval); progressFill.style.backgroundColor = "green"; - statusCell.textContent = "✔"; + statusCell.textContent = "?"; statusCell.style.color = "green"; // Increment completed files counter and check if all are done @@ -155,4 +155,4 @@ - \ No newline at end of file + diff --git a/samples/msgext-thirdparty-storage/csharp/Views/Shared/_Layout.cshtml b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Views/Shared/_Layout.cshtml similarity index 96% rename from samples/msgext-thirdparty-storage/csharp/Views/Shared/_Layout.cshtml rename to samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Views/Shared/_Layout.cshtml index 259de24736..bedd7af0a3 100644 --- a/samples/msgext-thirdparty-storage/csharp/Views/Shared/_Layout.cshtml +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Views/Shared/_Layout.cshtml @@ -1,4 +1,4 @@ - + Microsoft Teams Third-party storage capability Sample App diff --git a/samples/msgext-thirdparty-storage/csharp/Views/_ViewStart.cshtml b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Views/_ViewStart.cshtml similarity index 81% rename from samples/msgext-thirdparty-storage/csharp/Views/_ViewStart.cshtml rename to samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Views/_ViewStart.cshtml index a5f10045db..820a2f6e02 100644 --- a/samples/msgext-thirdparty-storage/csharp/Views/_ViewStart.cshtml +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/Views/_ViewStart.cshtml @@ -1,3 +1,3 @@ -@{ +@{ Layout = "_Layout"; } diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/appsettings.Development.json b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/appsettings.Development.json new file mode 100644 index 0000000000..cdf4a512f7 --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/appsettings.Development.json @@ -0,0 +1,20 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "", + "BaseUrl": "", + "TenantId": "" + } +} \ No newline at end of file diff --git a/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/appsettings.Playground.json b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/appsettings.Playground.json new file mode 100644 index 0000000000..0497106b3c --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/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-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/appsettings.json b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/appsettings.json new file mode 100644 index 0000000000..9e3379db53 --- /dev/null +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "" + } +} \ No newline at end of file diff --git a/samples/msgext-thirdparty-storage/csharp/wwwroot/css/styles.css b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/css/styles.css similarity index 99% rename from samples/msgext-thirdparty-storage/csharp/wwwroot/css/styles.css rename to samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/css/styles.css index 64a47bc144..a30619b0ea 100644 --- a/samples/msgext-thirdparty-storage/csharp/wwwroot/css/styles.css +++ b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/css/styles.css @@ -68,4 +68,4 @@ body { #uploadBtn.enabled { background-color: #5b5fc7; cursor: pointer; -} \ No newline at end of file +} diff --git a/samples/msgext-thirdparty-storage/csharp/wwwroot/icons/ExcelIcon.png b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/icons/ExcelIcon.png similarity index 100% rename from samples/msgext-thirdparty-storage/csharp/wwwroot/icons/ExcelIcon.png rename to samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/icons/ExcelIcon.png diff --git a/samples/msgext-thirdparty-storage/csharp/wwwroot/icons/ImageIcon.png b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/icons/ImageIcon.png similarity index 100% rename from samples/msgext-thirdparty-storage/csharp/wwwroot/icons/ImageIcon.png rename to samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/icons/ImageIcon.png diff --git a/samples/msgext-thirdparty-storage/csharp/wwwroot/icons/PdfIcon.png b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/icons/PdfIcon.png similarity index 100% rename from samples/msgext-thirdparty-storage/csharp/wwwroot/icons/PdfIcon.png rename to samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/icons/PdfIcon.png diff --git a/samples/msgext-thirdparty-storage/csharp/wwwroot/icons/WordIcons.png b/samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/icons/WordIcons.png similarity index 100% rename from samples/msgext-thirdparty-storage/csharp/wwwroot/icons/WordIcons.png rename to samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage/wwwroot/icons/WordIcons.png diff --git a/samples/msgext-thirdparty-storage/csharp/Views/Shared/_ValidationScriptsPartial.cshtml b/samples/msgext-thirdparty-storage/csharp/Views/Shared/_ValidationScriptsPartial.cshtml deleted file mode 100644 index ed86611b33..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/Views/Shared/_ValidationScriptsPartial.cshtml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/samples/msgext-thirdparty-storage/csharp/appsettings.json b/samples/msgext-thirdparty-storage/csharp/appsettings.json deleted file mode 100644 index f38b2274a8..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/appsettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "MicrosoftAppType": "MultiTenant", - "MicrosoftAppId": "{{MicrosoftAppId}}", - "MicrosoftAppPassword": "{{MicrosoftAppPassword}}", - "MicrosoftAppTenantId": "{{MicrosoftAppTenantId}}", - "BaseUrl": "{{BaseUrl}}" -} \ No newline at end of file diff --git a/samples/msgext-thirdparty-storage/csharp/wwwroot/default.html b/samples/msgext-thirdparty-storage/csharp/wwwroot/default.html deleted file mode 100644 index 7d5c18f77e..0000000000 --- a/samples/msgext-thirdparty-storage/csharp/wwwroot/default.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - - Teams Third-party storage Messaging Extension - - - - - - - - - Third-party storage - - - - - 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
Press the save button to continue.