-
Notifications
You must be signed in to change notification settings - Fork 487
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4264 from microsoft/johtaylo/issue4072
Johtaylo/issue4072
- Loading branch information
Showing
32 changed files
with
1,245 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Threading.Tasks; | ||
using Microsoft.Bot.Builder.Integration.AspNet.Core; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.BotBuilderSamples | ||
{ | ||
public class AdapterWithErrorHandler : BotFrameworkHttpAdapter | ||
{ | ||
public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger) | ||
: base(configuration, logger) | ||
{ | ||
OnTurnError = (turnContext, exception) => | ||
{ | ||
// Log any leaked exception from the application. | ||
logger.LogError($"Exception caught : {exception.Message}"); | ||
return Task.CompletedTask; | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Security.Claims; | ||
using System.Threading.Tasks; | ||
using Microsoft.Bot.Connector.Authentication; | ||
|
||
namespace Microsoft.BotBuilderSamples.SimpleRootBot.Authentication | ||
{ | ||
/// <summary> | ||
/// Sample claims validator that loads an allowed list from configuration if present | ||
/// and checks that responses are coming from configured skills. | ||
/// </summary> | ||
public class AllowedSkillsClaimsValidator : ClaimsValidator | ||
{ | ||
private readonly List<string> _allowedSkills; | ||
|
||
public AllowedSkillsClaimsValidator(SkillsConfiguration skillsConfig) | ||
{ | ||
if (skillsConfig == null) | ||
{ | ||
throw new ArgumentNullException(nameof(skillsConfig)); | ||
} | ||
|
||
// Load the appIds for the configured skills (we will only allow responses from skills we have configured). | ||
_allowedSkills = (from skill in skillsConfig.Skills.Values select skill.AppId).ToList(); | ||
} | ||
|
||
public override Task ValidateClaimsAsync(IList<Claim> claims) | ||
{ | ||
if (SkillValidation.IsSkillClaim(claims)) | ||
{ | ||
// Check that the appId claim in the skill request is in the list of skills configured for this bot. | ||
var appId = JwtTokenValidation.GetAppIdFromClaims(claims); | ||
if (!_allowedSkills.Contains(appId)) | ||
{ | ||
throw new UnauthorizedAccessException($"Received a request from an application with an appID of \"{appId}\". To enable requests from this skill, add the skill to your configuration file."); | ||
} | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// 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.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Bot.Builder; | ||
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills; | ||
using Microsoft.Bot.Builder.Skills; | ||
using Microsoft.Bot.Connector.Authentication; | ||
using Microsoft.Bot.Schema; | ||
using Microsoft.BotBuilderSamples.SimpleRootBot; | ||
using Microsoft.Extensions.Configuration; | ||
using Newtonsoft.Json; | ||
|
||
namespace Bot1 | ||
{ | ||
public class Bot1 : ActivityHandler | ||
{ | ||
public static readonly string ActiveSkillPropertyName = $"{typeof(Bot1).FullName}.ActiveSkillProperty"; | ||
private readonly IStatePropertyAccessor<BotFrameworkSkill> _activeSkillProperty; | ||
private readonly string _botId; | ||
private readonly ConversationState _conversationState; | ||
private readonly SkillHttpClient _skillClient; | ||
private readonly SkillsConfiguration _skillsConfig; | ||
private readonly BotFrameworkSkill _targetSkill; | ||
|
||
public Bot1(ConversationState conversationState, SkillsConfiguration skillsConfig, SkillHttpClient skillClient, IConfiguration configuration) | ||
{ | ||
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState)); | ||
_skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig)); | ||
_skillClient = skillClient ?? throw new ArgumentNullException(nameof(skillsConfig)); | ||
if (configuration == null) | ||
{ | ||
throw new ArgumentNullException(nameof(configuration)); | ||
} | ||
|
||
_botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value; | ||
if (string.IsNullOrWhiteSpace(_botId)) | ||
{ | ||
throw new ArgumentException($"{MicrosoftAppCredentials.MicrosoftAppIdKey} is not set in configuration"); | ||
} | ||
|
||
// We use a single skill in this example. | ||
var targetSkillId = "NextBot"; | ||
if (!_skillsConfig.Skills.TryGetValue(targetSkillId, out _targetSkill)) | ||
{ | ||
throw new ArgumentException($"Skill with ID \"{targetSkillId}\" not found in configuration"); | ||
} | ||
|
||
// Create state property to track the active skill | ||
_activeSkillProperty = conversationState.CreateProperty<BotFrameworkSkill>(ActiveSkillPropertyName); | ||
} | ||
|
||
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) | ||
{ | ||
// Forward all activities except EndOfConversation to the skill. | ||
if (turnContext.Activity.Type != ActivityTypes.EndOfConversation) | ||
{ | ||
// Try to get the active skill | ||
var activeSkill = await _activeSkillProperty.GetAsync(turnContext, () => null, cancellationToken); | ||
if (activeSkill != null) | ||
{ | ||
// Send the activity to the skill | ||
await SendToSkill(turnContext, activeSkill, cancellationToken); | ||
return; | ||
} | ||
} | ||
|
||
await base.OnTurnAsync(turnContext, cancellationToken); | ||
|
||
// Save any state changes that might have occured during the turn. | ||
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); | ||
} | ||
|
||
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) | ||
{ | ||
if (turnContext.Activity.Text.Contains("skill")) | ||
{ | ||
await turnContext.SendActivityAsync(MessageFactory.Text("Got it, connecting you to the skill..."), cancellationToken); | ||
|
||
// Save active skill in state | ||
await _activeSkillProperty.SetAsync(turnContext, _targetSkill, cancellationToken); | ||
|
||
// Send the activity to the skill | ||
await SendToSkill(turnContext, _targetSkill, cancellationToken); | ||
return; | ||
} | ||
|
||
// just respond | ||
await turnContext.SendActivityAsync(MessageFactory.Text("Me no nothin'. Say \"skill\" and I'll patch you through"), cancellationToken); | ||
|
||
// Save conversation state | ||
await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken); | ||
} | ||
|
||
protected override async Task OnEndOfConversationActivityAsync(ITurnContext<IEndOfConversationActivity> turnContext, CancellationToken cancellationToken) | ||
{ | ||
// forget skill invocation | ||
await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken); | ||
|
||
// Show status message, text and value returned by the skill | ||
var eocActivityMessage = $"Received {ActivityTypes.EndOfConversation}.\n\nCode: {turnContext.Activity.Code}"; | ||
if (!string.IsNullOrWhiteSpace(turnContext.Activity.Text)) | ||
{ | ||
eocActivityMessage += $"\n\nText: {turnContext.Activity.Text}"; | ||
} | ||
|
||
if ((turnContext.Activity as Activity)?.Value != null) | ||
{ | ||
eocActivityMessage += $"\n\nValue: {JsonConvert.SerializeObject((turnContext.Activity as Activity)?.Value)}"; | ||
} | ||
|
||
await turnContext.SendActivityAsync(MessageFactory.Text(eocActivityMessage), cancellationToken); | ||
|
||
// We are back at the root | ||
await turnContext.SendActivityAsync(MessageFactory.Text("Back in the root bot. Say \"skill\" and I'll patch you through"), cancellationToken); | ||
|
||
// Save conversation state | ||
await _conversationState.SaveChangesAsync(turnContext, cancellationToken: cancellationToken); | ||
} | ||
|
||
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken) | ||
{ | ||
foreach (var member in membersAdded) | ||
{ | ||
if (member.Id != turnContext.Activity.Recipient.Id) | ||
{ | ||
await turnContext.SendActivityAsync(MessageFactory.Text("Hello and welcome!"), cancellationToken); | ||
} | ||
} | ||
} | ||
|
||
private async Task SendToSkill(ITurnContext turnContext, BotFrameworkSkill targetSkill, CancellationToken cancellationToken) | ||
{ | ||
// NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill | ||
// will have access to current accurate state. | ||
await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken); | ||
|
||
// route the activity to the skill | ||
var response = await _skillClient.PostActivityAsync(_botId, targetSkill, _skillsConfig.SkillHostEndpoint, turnContext.Activity, cancellationToken); | ||
|
||
// Check response status | ||
if (!response.IsSuccessStatusCode()) | ||
{ | ||
throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {response.Status}). \r\n {response.Body}"); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.