diff --git a/samples/24.bot-authentication-msgraph/LICENSE b/samples/24.bot-authentication-msgraph/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/24.bot-authentication-msgraph/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/samples/24.bot-authentication-msgraph/README.md b/samples/24.bot-authentication-msgraph/README.md new file mode 100644 index 000000000..cdcfaa5ba --- /dev/null +++ b/samples/24.bot-authentication-msgraph/README.md @@ -0,0 +1,118 @@ +# Authentication Bot Utilizing MS Graph + +Bot Framework v4 bot authentication using Microsoft Graph sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), is shows how to use the bot authentication capabilities of Azure Bot Service. 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/en-us/azure/bot-service/bot-builder-authentication?view=azure-bot-service-4.0&tabs=csharp) for information about getting an AADv2 +application setup for use in Azure Bot Service. The [scopes](https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference) used in this sample are the following: + +- `openid` +- `profile` +- `User.Read` + +NOTE: Microsoft Teams currently differs slightly in the way auth is integrated with the bot. Refer to sample 46.teams-auth. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-authentication-msgraph-sample.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment sub create --name "authenticationBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="authenticationGraphBotPlan" newWebAppName="authenticationGraphBot" groupLocation="westus" newAppServicePlanLocation="westus"` + +#### To an existing Resource Group +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="authenticationGraphBot" newAppServicePlanName="authenticationGraphBotPlan" appServicePlanLocation="westus" --name "authenticationGraphBot"` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + +## Interacting with the bot + +This sample uses the bot authentication capabilities of Azure Bot Service, providing features to make it easier to develop a bot that +authenticates users to various identity providers such as Azure AD (Azure Active Directory), GitHub, Uber, and so on. These updates also +take steps towards an improved user experience by eliminating the magic code verification for some clients and channels. +It is important to note that the user's token does not need to be stored in the bot. When the bot needs to use or verify the user has a valid token at any point the OAuth prompt may be sent. If the token is not valid they will be prompted to login. + +## Microsoft Graph API + +This sample demonstrates using Azure Active Directory v2 as the OAuth2 provider and utilizes the Microsoft Graph API. +Microsoft Graph is a Microsoft developer platform that connects multiple services and devices. Initially released in 2015, +the Microsoft Graph builds on Office 365 APIs and allows developers to integrate their services with Microsoft products including Windows, Office 365, and Azure. + +## GraphError 404: ResourceNotFound, Resource could not be discovered + +This error may confusingly present itself if either of the following are true: + +- You're using an email ending in `@microsoft.com`, and/or +- Your OAuth AAD tenant is `microsoft.onmicrosoft.com`. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Microsoft Graph API](https://developer.microsoft.com/en-us/graph) +- [MS Graph Docs](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) and [SDK](https://github.com/microsoftgraph/msgraph-sdk-dotnet) +- [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) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-new-rg.json b/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App 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." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "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/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-preexisting-rg.json b/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App 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. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/24.bot-authentication-msgraph/pom.xml b/samples/24.bot-authentication-msgraph/pom.xml new file mode 100644 index 000000000..29f9ee4e7 --- /dev/null +++ b/samples/24.bot-authentication-msgraph/pom.xml @@ -0,0 +1,249 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-authentication-msgraph + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains the Bot Authentication using MSGraph sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.authentication.Application + + + + + com.microsoft.graph + microsoft-graph + 2.6.0 + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview8 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.authentication.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/Application.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/Application.java new file mode 100644 index 000000000..c4ebeaacf --- /dev/null +++ b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/Application.java @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + Configuration configuration, + ConversationState conversationState, + UserState userState, + MainDialog dialog + ) { + return new AuthBot(conversationState, userState, new MainDialog(configuration)); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java new file mode 100644 index 000000000..5f189abc0 --- /dev/null +++ b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import java.util.concurrent.CompletableFuture; +import java.util.List; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.schema.ChannelAccount; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.bot.schema.Activity; +import org.apache.commons.lang3.StringUtils; + +public class AuthBot extends DialogBot { + + public AuthBot(ConversationState conversationState, UserState userState, MainDialog dialog) { + super(conversationState, userState, dialog); + } + + @Override + protected CompletableFuture onMembersAdded( + List membersAdded, TurnContext turnContext + ) { + return turnContext.getActivity().getMembersAdded().stream() + .filter(member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(channel -> { + Activity reply = MessageFactory.text("Welcome to AuthBot on MSGraph." + + " Type anything to get logged in. Type 'logout' to sign-out."); + + return turnContext.sendActivity(reply); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponse -> null); + } + + @Override + protected CompletableFuture onTokenResponseEvent(TurnContext turnContext) { + // Run the Dialog with the new Token Response Event Activity. + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } + +} diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java new file mode 100644 index 000000000..83db0b694 --- /dev/null +++ b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import java.util.concurrent.CompletableFuture; + +/** + * This Bot implementation can run any type of Dialog. The use of type parameterization is to 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. + */ +public class DialogBot extends ActivityHandler { + + protected Dialog dialog; + protected BotState conversationState; + protected BotState userState; + + public DialogBot( + ConversationState withConversationState, + UserState withUserState, + T withDialog + ) { + dialog = withDialog; + conversationState = withConversationState; + userState = withUserState; + } + + @Override + public CompletableFuture onTurn( + TurnContext turnContext + ) { + return super.onTurn(turnContext) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + @Override + protected CompletableFuture onMessageActivity( + TurnContext turnContext + ) { + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java new file mode 100644 index 000000000..dcb42dd31 --- /dev/null +++ b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.BotFrameworkAdapter; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.schema.ActivityTypes; + +public class LogoutDialog extends ComponentDialog { + + private final String connectionName; + + public LogoutDialog(String id, String connectionName) { + super(id); + this.connectionName = connectionName; + } + + + @Override + protected CompletableFuture onBeginDialog( + DialogContext innerDc, Object options + ) { + DialogTurnResult result = interrupt(innerDc).join(); + if (result != null) { + return CompletableFuture.completedFuture(result); + } + + return super.onBeginDialog(innerDc, options); + } + + @Override + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + DialogTurnResult result = interrupt(innerDc).join(); + if (result != null) { + return CompletableFuture.completedFuture(result); + } + + return super.onContinueDialog(innerDc); + } + + private CompletableFuture interrupt(DialogContext innerDc) { + if (innerDc.getContext().getActivity().getType().equals(ActivityTypes.MESSAGE)) { + String text = innerDc.getContext().getActivity().getText().toLowerCase(); + + if (text.equals("logout")) { + // The bot adapter encapsulates the authentication processes. + BotFrameworkAdapter botAdapter = (BotFrameworkAdapter) innerDc.getContext() + .getAdapter(); + botAdapter.signOutUser(innerDc.getContext(), getConnectionName(), null).join(); + innerDc.getContext().sendActivity(MessageFactory.text("You have been signed out.")) + .join(); + return innerDc.cancelAllDialogs(); + } + } + + return CompletableFuture.completedFuture(null); + } + + /** + * @return the ConnectionName value as a String. + */ + protected String getConnectionName() { + return this.connectionName; + } + +} + diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java new file mode 100644 index 000000000..17140fc42 --- /dev/null +++ b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.ChoicePrompt; +import com.microsoft.bot.dialogs.prompts.OAuthPrompt; +import com.microsoft.bot.dialogs.prompts.OAuthPromptSettings; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.schema.TokenResponse; + +import org.springframework.stereotype.Component; + +@Component +class MainDialog extends LogoutDialog { + + public MainDialog(Configuration configuration) { + super("MainDialog", configuration.getProperty("ConnectionName")); + + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setConnectionName(""); + settings.setText("Please login"); + settings.setTitle("Login"); + settings.setConnectionName(configuration.getProperty("ConnectionName")); + settings.setTimeout(300000); // User has 5 minutes to login (1000 * 60 * 5) + + addDialog(new OAuthPrompt("OAuthPrompt", settings)); + + addDialog(new TextPrompt("TextPrompt")); + + WaterfallStep[] waterfallSteps = { + this::promptStep, + this::loginStep, + this::commandStep, + this::processStep + }; + + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture promptStep(WaterfallStepContext stepContext) { + return stepContext.beginDialog("OAuthPrompt", null); + } + + private CompletableFuture loginStep(WaterfallStepContext stepContext) { + // Get the token from the previous step. Note that we could also have gotten the + // token directly from the prompt itself. There instanceof an example of this in the next method. + TokenResponse tokenResponse = (TokenResponse) stepContext.getResult(); + if (tokenResponse != null) { + stepContext.getContext().sendActivity(MessageFactory.text("You are now logged in.")).join(); + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Would you like to do? (type 'me', or 'email')")); + return stepContext.prompt("TextPrompt", options); + } + + stepContext.getContext().sendActivity( + MessageFactory.text("Login was not successful please try again.")).join(); + return stepContext.endDialog(); + } + + private CompletableFuture commandStep( + WaterfallStepContext stepContext + ) { + + stepContext.getValues().put("command", stepContext.getResult()); + stepContext.getContext().sendActivity(MessageFactory.text("Thank you.")).join(); + + // Call the prompt again because we need the token. The reasons for this are: + // 1. If the user instanceof already logged in we do not need to store the token locally in the bot and worry + // about refreshing it. We can always just call the prompt again to get the token. + // 2. We never know how long it will take a user to respond. By the time the + // user responds the token may have expired. The user would then be prompted to login again. + // + // There instanceof no reason to store the token locally in the bot because we can always just call + // the OAuth prompt to get the token or get a new token if needed. + return stepContext.beginDialog("OAuthPrompt"); + + } + + private CompletableFuture processStep(WaterfallStepContext stepContext) { + if (stepContext.getResult() != null) { + // We do not need to store the token in the bot. When we need the token we can + // send another prompt. If the token is valid the user will not need to log back in. + // The token will be available in the Result property of the task. + TokenResponse tokenResponse = null; + if (stepContext.getResult() instanceof TokenResponse) { + tokenResponse = (TokenResponse) stepContext.getResult(); + } + + // If we have the token use the user is authenticated so we may use it to make API calls. + if (tokenResponse != null && tokenResponse.getToken() != null) { + + String command = ""; + if (stepContext.getValues() != null + && stepContext.getValues().get("command") != null + && stepContext.getValues().get("command") instanceof String) { + command = ((String) stepContext.getValues().get("command")).toLowerCase(); + }; + + if (command.equals("me")) { + OAuthHelpers.ListMeAsync(stepContext.getContext(), tokenResponse).join(); + } else if (command.startsWith("email")) { + OAuthHelpers.ListEmailAddressAsync(stepContext.getContext(), tokenResponse).join(); + } else { + stepContext.getContext().sendActivity( + MessageFactory.text(String.format("Your token is: %s", tokenResponse.getToken()))).join(); + } + } + } else { + stepContext.getContext().sendActivity( + MessageFactory.text("We couldn't log you in. Please try again later.")).join(); + } + + return stepContext.endDialog(); + } +} + diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/OAuthHelpers.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/OAuthHelpers.java new file mode 100644 index 000000000..f3ecb9c9f --- /dev/null +++ b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/OAuthHelpers.java @@ -0,0 +1,37 @@ +package com.microsoft.bot.sample.authentication; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.schema.TokenResponse; +import com.microsoft.graph.models.extensions.User; + +public class OAuthHelpers { + // Send the user their Graph Display Name from the bot. + public static CompletableFuture ListMeAsync(TurnContext turnContext, + TokenResponse tokenResponse) { + User user = getUser(turnContext, tokenResponse); + return turnContext.sendActivity(String.format("You are %s.", user.displayName)).thenApply(result -> null); + } + + // Send the user their Graph Email Address from the bot. + public static CompletableFuture ListEmailAddressAsync(TurnContext turnContext, + TokenResponse tokenResponse) { + User user = getUser(turnContext, tokenResponse); + return turnContext.sendActivity(String.format("Your email: %s.", user.mail)).thenApply(result -> null); + } + + private static User getUser(TurnContext turnContext, TokenResponse tokenResponse) { + if (turnContext == null) { + throw new IllegalArgumentException("turnContext cannot be null"); + } + + if (tokenResponse == null) { + throw new IllegalArgumentException("tokenResponse cannot be null"); + } + + // Pull in the data from the Microsoft Graph. + SimpleGraphClient client = new SimpleGraphClient(tokenResponse.getToken()); + return client.getMe(); + } +} diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/SimpleGraphClient.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/SimpleGraphClient.java new file mode 100644 index 000000000..54a7c9c55 --- /dev/null +++ b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/SimpleGraphClient.java @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import com.microsoft.graph.logger.DefaultLogger; +import com.microsoft.graph.logger.LoggerLevel; +import com.microsoft.graph.models.extensions.User; +import com.microsoft.graph.options.Option; +import com.microsoft.graph.requests.extensions.GraphServiceClient; +import com.microsoft.graph.models.extensions.IGraphServiceClient; + +import java.util.LinkedList; +import java.util.List; + +public class SimpleGraphClient { + private String token; + public SimpleGraphClient(String token) { + this.token = token; + } + + public User getMe() { + IGraphServiceClient client = getAuthenticatedClient(); + final List