Skip to content

Commit

Permalink
Add Teams manifest, update README, add Graph client (microsoft#2149)
Browse files Browse the repository at this point in the history
* Added documentation on how to set up in Teams

* Added Graph API call

* Logout now works even w/bot mention in channels

* Add manifest, update readme

* fix packages

Co-authored-by: Bob German <[email protected]>
  • Loading branch information
2 people authored and johnataylor committed Jan 23, 2020
1 parent 66b04f2 commit 89ae930
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Graph" Version="1.17.0" />
<PackageReference Include="Microsoft.Graph" Version="1.21.0" />
<PackageReference Include="Microsoft.Bot.Builder.Dialogs" Version="4.7.0" />
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Version="4.7.0" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public LogoutDialog(string id, string connectionName)
{
var text = innerDc.Context.Activity.Text.ToLowerInvariant();

if (text == "logout")
// Allow logout anywhere in the command
if (text.IndexOf("logout") > 0)
{
// The bot adapter encapsulates the authentication processes.
var botAdapter = (BotFrameworkAdapter)innerDc.Context.Adapter;
Expand Down
11 changes: 9 additions & 2 deletions samples/csharp_dotnetcore/46.teams-auth/Dialogs/MainDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,16 @@ private async Task<DialogTurnResult> LoginStepAsync(WaterfallStepContext stepCon
// Get the token from the previous step. Note that we could also have gotten the
// token directly from the prompt itself. There is an example of this in the next method.
var tokenResponse = (TokenResponse)stepContext.Result;
if (tokenResponse != null)
if (tokenResponse?.Token != null)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("You are now logged in."), cancellationToken);
// Pull in the data from the Microsoft Graph.
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}); you job title is: {title}");

return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Would you like to view your token?") }, cancellationToken);
}

Expand Down
68 changes: 32 additions & 36 deletions samples/csharp_dotnetcore/46.teams-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

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 building a bot for Teams.
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.

At this stage the primary focus of this sample is how to use the Bot Framework support for oauth in your bot. The reason for prioritizing this is that 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 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 Teams channel is also capable of sending Message Reaction Activities and virtual methods for these are included in the `TeamsActivityHandler`. A Message Reaction Activity references the original Activity using the replyToId. This id would have actually been the value returned from a previous Message Activity the bot had sent. This activity should also be visible through the Activity Feed in Microsoft Teams, documentation for which can be found here https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/activity-feed
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 Azure AD (Azure Active Directory), 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://docs.botframework.com), 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, etc.
> 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.
## Prerequisites

- Microsoft Teams is installed and you have an account (not a guest account)
- [.NET Core SDK](https://dotnet.microsoft.com/download) version 2.1
- [ngrok](https://ngrok.com/) or equivalent tunnelling solution

```bash
# determine dotnet version
Expand All @@ -21,51 +23,45 @@ The sample uses the bot authentication capabilities in [Azure Bot Service](https

## To try this sample

- Clone the repository
> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because
> the Teams service needs to call into the bot.
1) Clone the repository

```bash
git clone https://github.com/microsoft/botbuilder-samples.git
git clone https://github.com/Microsoft/botbuilder-samples.git
```

- Deploy your bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment)

- [Add Authentication to your bot via Azure Bot Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-authentication?view=azure-bot-service-4.0&tabs=csharp)

After Authentication has been configured via Azure Bot Service, you can test the bot.

- In a terminal, navigate to `samples/csharp_dotnetcore/46.teams-auth`
- Run the bot from a terminal or from Visual Studio, choose option A or B.

A) From a terminal
1) If you are using Visual Studio
- Open the solution `samples\csharp_dotnetcore\csharp_dotnetcore.sln`
- Set the Startup Project to `TeamsTaskModule`
- The changes specified here will apply to this project

```bash
# run the bot
dotnet run
```

B) Or from Visual Studio
1) Run ngrok - point to port 3978

- Launch Visual Studio
- File -> Open -> Project/Solution
- Navigate to `samples/csharp_dotnetcore/46.teams-auth` folder
- Select `TeamsAuth.csproj` file
- Press `F5` to run the project
```bash
ngrok http -host-header=rewrite 3978
```

## Testing the bot using Bot Framework Emulator
1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure
- Use the current `https` URL you were given by running ngrok. Append with the path `/api/messages` used by this sample
- Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/en-us/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0)
- __*If you don't have an Azure account*__ you can use this [Bot Framework registration](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework)

[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.
1) Update the `appsettings.json` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.)

- Install the Bot Framework Emulator version 4.5.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
1) __*This step is specific to Teams.*__
- **Edit** the `manifest.json` contained in the `teamsAppManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<<YOUR-MICROSOFT-APP-ID>>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`)
- **Zip** up the contents of the `teamsAppManifest` folder to create a `manifest.zip`
- **Upload** the `manifest.zip` to Teams (in the Apps view click "Upload a custom app")
### Connect to the bot using Bot Framework Emulator
1) Run your bot, either from Visual Studio with `F5` or using `dotnet run` in the appropriate folder.
- Launch Bot Framework Emulator
- File -> Open Bot
- Enter a Bot URL of `http://localhost:3978/api/messages`
## Interacting with the bot in Teams
## Authentication
> Note this `manifest.json` specified that the bot will be installed in a "personal" scope only. Please refer to Teams documentation for more details.
This sample uses bot authentication capabilities in Azure Bot Service. Azure Bot Service provides 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, etc. These updates also take steps towards an improved user experience by eliminating the magic code verification for some clients.
You can interact with this bot by sending it a message. The bot will respond by requesting you to login to AAD, then making a call to the Graph API on your behalf and returning the results.
## Deploy the bot to Azure
Expand Down
152 changes: 152 additions & 0 deletions samples/csharp_dotnetcore/46.teams-auth/SimpleGraphClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// 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;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Graph;

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;

public SimpleGraphClient(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
throw new ArgumentNullException(nameof(token));
}

_token = token;
}

// Sends an email on the users behalf using the Microsoft Graph API
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<Recipient>
{
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(email, true).Request().PostAsync();
}

// Gets mail for the user using the Microsoft Graph API
public async Task<Message[]> GetRecentMailAsync()
{
var graphClient = GetAuthenticatedClient();
var messages = await graphClient.Me.MailFolders.Inbox.Messages.Request().GetAsync();
return messages.Take(5).ToArray();
}

// Get information about the user.
public async Task<User> GetMeAsync()
{
var graphClient = GetAuthenticatedClient();
var me = await graphClient.Me.Request().GetAsync();
return me;
}

// gets information about the user's manager.
public async Task<User> GetManagerAsync()
{
var graphClient = GetAuthenticatedClient();
var manager = await graphClient.Me.Manager.Request().GetAsync() as User;
return manager;
}

// // Gets the user's photo
// public async Task<PhotoResponse> 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;
// }
// }

// Get an Authenticated Microsoft Graph client using the token issued to the user.
private GraphServiceClient GetAuthenticatedClient()
{
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
requestMessage =>
{
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", _token);

// Get event times in the current time zone.
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");

return Task.CompletedTask;
}));
return graphClient;
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
"manifestVersion": "1.5",
"version": "1.0.0",
"id": "<<YOUR-MICROSOFT-APP-ID>>",
"packageName": "com.microsoft.teams.samples",
"developer": {
"name": "Microsoft",
"websiteUrl": "https://example.azurewebsites.net",
"privacyUrl": "https://example.azurewebsites.net/privacy",
"termsOfUseUrl": "https://example.azurewebsites.net/termsofuse"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"name": {
"short": "Task Module",
"full": "Simple Task Module"
},
"description": {
"short": "Test Task Module Scenario",
"full": "Simple Task Module Scenario Test"
},
"accentColor": "#FFFFFF",
"bots": [
{
"botId": "<<YOUR-MICROSOFT-APP-ID>>",
"scopes": [
"personal"
],
"supportsFiles": false,
"isNotificationOnly": false
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"token.botframework.com",
"*.ngrok.io"
]
}
1 change: 1 addition & 0 deletions samples/csharp_dotnetcore/46.teams-auth/TeamsAuth.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.Graph" Version="1.21.0" />
<PackageReference Include="Microsoft.Bot.Builder.Dialogs" Version="4.7.0" />
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Version="4.7.0" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PackageReference Include="AdaptiveCards" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Version="4.7.0" />
<PackageReference Include="Microsoft.Graph" Version="1.19.0" />
<PackageReference Include="Microsoft.Graph" Version="1.21.0" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit 89ae930

Please sign in to comment.