Skip to content

Commit c42dcde

Browse files
Eric Dahlvanggabog
andauthored
Add originatingAudience to SkillHttpClient.PostActivityAsync (#3477)
* Add originatingAudience to SkillHttpClient.PostActivityAsync * Deprecated old methods in SkillCOnversationIdFactoryBase and added new ones. Updated related classes to use the new methods. * clone ConversationReference for FunctionalTest's SkillConversationIdFactory * change skill conversation.id in SimpleRootBot example * Skill Unit test updates Co-authored-by: Gabo Gilabert <[email protected]>
1 parent 3ee7ebf commit c42dcde

File tree

9 files changed

+309
-44
lines changed

9 files changed

+309
-44
lines changed

FunctionalTests/Skills/SimpleBotToBot/SimpleRootBot/SkillConversationIdFactory.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@ public class SkillConversationIdFactory : SkillConversationIdFactoryBase
1818
{
1919
private readonly ConcurrentDictionary<string, string> _conversationRefs = new ConcurrentDictionary<string, string>();
2020

21-
public override Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
21+
public override Task<string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken)
2222
{
23-
var crJson = JsonConvert.SerializeObject(conversationReference);
24-
var key = $"{conversationReference.Conversation.Id}-{conversationReference.ChannelId}-skillconvo";
25-
_conversationRefs.GetOrAdd(key, crJson);
23+
var skillConversationReference = new SkillConversationReference
24+
{
25+
ConversationReference = options.Activity.GetConversationReference(),
26+
OAuthScope = options.FromBotOAuthScope
27+
};
28+
var key = $"{options.FromBotId}-{options.BotFrameworkSkill.AppId}-{skillConversationReference.ConversationReference.Conversation.Id}-{skillConversationReference.ConversationReference.ChannelId}-skillconvo";
29+
_conversationRefs.GetOrAdd(key, JsonConvert.SerializeObject(skillConversationReference));
2630
return Task.FromResult(key);
2731
}
2832

29-
public override Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
33+
public override Task<SkillConversationReference> GetSkillConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
3034
{
31-
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(_conversationRefs[skillConversationId]);
35+
var conversationReference = JsonConvert.DeserializeObject<SkillConversationReference>(_conversationRefs[skillConversationId]);
3236
return Task.FromResult(conversationReference);
3337
}
3438

libraries/Microsoft.Bot.Builder/ChannelServiceHandler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public ChannelServiceHandler(
3838
_channelProvider = channelProvider;
3939
}
4040

41+
protected IChannelProvider ChannelProvider => _channelProvider;
42+
4143
public async Task<ResourceResponse> HandleSendToConversationAsync(string authHeader, string conversationId, Activity activity, CancellationToken cancellationToken = default)
4244
{
4345
var claimsIdentity = await AuthenticateAsync(authHeader).ConfigureAwait(false);

libraries/Microsoft.Bot.Builder/Skills/SkillConversationIdFactoryBase.cs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using System;
45
using System.Threading;
56
using System.Threading.Tasks;
67
using Microsoft.Bot.Schema;
@@ -21,20 +22,53 @@ public abstract class SkillConversationIdFactoryBase
2122
/// <remarks>
2223
/// It should be possible to use the returned string on a request URL and it should not contain special characters.
2324
/// </remarks>
24-
public abstract Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken);
25+
[Obsolete("Method is deprecated, please use CreateSkillConversationIdAsync() with SkillConversationIdFactoryOptions instead.", false)]
26+
public virtual Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
27+
{
28+
throw new NotImplementedException();
29+
}
2530

2631
/// <summary>
27-
/// Gets the <see cref="ConversationReference"/> created using <see cref="CreateSkillConversationIdAsync"/> for a skillConversationId.
32+
/// Creates a conversation id for a skill conversation.
2833
/// </summary>
29-
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync"/>.</param>
34+
/// <param name="options">A <see cref="SkillConversationIdFactoryOptions"/> instance containing parameters for creating the conversation ID.</param>
35+
/// <param name="cancellationToken">A cancellation token.</param>
36+
/// <returns>A unique conversation ID used to communicate with the skill.</returns>
37+
/// <remarks>
38+
/// It should be possible to use the returned string on a request URL and it should not contain special characters.
39+
/// </remarks>
40+
public virtual Task<string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken)
41+
{
42+
throw new NotImplementedException();
43+
}
44+
45+
/// <summary>
46+
/// Gets the <see cref="ConversationReference"/> created using <see cref="CreateSkillConversationIdAsync(Microsoft.Bot.Schema.ConversationReference,System.Threading.CancellationToken)"/> for a skillConversationId.
47+
/// </summary>
48+
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync(Microsoft.Bot.Schema.ConversationReference,System.Threading.CancellationToken)"/>.</param>
3049
/// <param name="cancellationToken">A cancellation token.</param>
3150
/// <returns>The caller's <see cref="ConversationReference"/> for a skillConversationId. null if not found.</returns>
32-
public abstract Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken);
51+
[Obsolete("Method is deprecated, please use GetSkillConversationReferenceAsync() instead.", false)]
52+
public virtual Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
53+
{
54+
throw new NotImplementedException();
55+
}
56+
57+
/// <summary>
58+
/// Gets the <see cref="SkillConversationReference"/> used during <see cref="CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions,System.Threading.CancellationToken)"/> for a skillConversationId.
59+
/// </summary>
60+
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions,System.Threading.CancellationToken)"/>.</param>
61+
/// <param name="cancellationToken">A cancellation token.</param>
62+
/// <returns>The caller's <see cref="ConversationReference"/> for a skillConversationId, with originatingAudience. Null if not found.</returns>
63+
public virtual Task<SkillConversationReference> GetSkillConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
64+
{
65+
throw new NotImplementedException();
66+
}
3367

3468
/// <summary>
3569
/// Deletes a <see cref="ConversationReference"/>.
3670
/// </summary>
37-
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync"/>.</param>
71+
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions,System.Threading.CancellationToken)"/>.</param>
3872
/// <param name="cancellationToken">A cancellation token.</param>
3973
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
4074
public abstract Task DeleteConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Bot.Schema;
5+
6+
namespace Microsoft.Bot.Builder.Skills
7+
{
8+
/// <summary>
9+
/// A class defining the parameters used in <see cref="SkillConversationIdFactoryBase.CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions,System.Threading.CancellationToken)"/>.
10+
/// </summary>
11+
public class SkillConversationIdFactoryOptions
12+
{
13+
/// <summary>
14+
/// Gets or sets the oauth audience scope, used during token retrieval (either https://api.botframework.com or bot app id).
15+
/// </summary>
16+
/// <value>
17+
/// The oauth audience scope, used during token retrieval (either https://api.botframework.com or bot app id if this is a skill calling another skill).
18+
/// </value>
19+
public string FromBotOAuthScope { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets the id of the parent bot that is messaging the skill.
23+
/// </summary>
24+
/// <value>
25+
/// The id of the parent bot that is messaging the skill.
26+
/// </value>
27+
public string FromBotId { get; set; }
28+
29+
/// <summary>
30+
/// Gets or sets the activity which will be sent to the skill.
31+
/// </summary>
32+
/// <value>
33+
/// The activity which will be sent to the skill.
34+
/// </value>
35+
public Activity Activity { get; set; }
36+
37+
/// <summary>
38+
/// Gets or sets the skill to create the conversation Id for.
39+
/// </summary>
40+
/// <value>
41+
/// The skill to create the conversation Id for.
42+
/// </value>
43+
public BotFrameworkSkill BotFrameworkSkill { get; set; }
44+
}
45+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Bot.Schema;
5+
6+
namespace Microsoft.Bot.Builder.Skills
7+
{
8+
public class SkillConversationReference
9+
{
10+
public ConversationReference ConversationReference { get; set; }
11+
12+
public string OAuthScope { get; set; }
13+
}
14+
}

libraries/Microsoft.Bot.Builder/Skills/SkillHandler.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class SkillHandler : ChannelServiceHandler
2323

2424
private readonly BotAdapter _adapter;
2525
private readonly IBot _bot;
26-
private readonly SkillConversationIdFactoryBase _conversationIdIdFactory;
26+
private readonly SkillConversationIdFactoryBase _conversationIdFactory;
2727
private readonly ILogger _logger;
2828

2929
/// <summary>
@@ -54,7 +54,7 @@ public SkillHandler(
5454
{
5555
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
5656
_bot = bot ?? throw new ArgumentNullException(nameof(bot));
57-
_conversationIdIdFactory = conversationIdFactory ?? throw new ArgumentNullException(nameof(conversationIdFactory));
57+
_conversationIdFactory = conversationIdFactory ?? throw new ArgumentNullException(nameof(conversationIdFactory));
5858
_logger = logger ?? NullLogger.Instance;
5959
}
6060

@@ -150,26 +150,43 @@ private static void ApplyEventToTurnContextActivity(ITurnContext turnContext, Ac
150150

151151
private async Task<ResourceResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string replyToActivityId, Activity activity, CancellationToken cancellationToken)
152152
{
153-
var conversationReference = await _conversationIdIdFactory.GetConversationReferenceAsync(conversationId, CancellationToken.None).ConfigureAwait(false);
153+
SkillConversationReference skillConversationReference = null;
154+
try
155+
{
156+
skillConversationReference = await _conversationIdFactory.GetSkillConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false);
157+
}
158+
catch (NotImplementedException)
159+
{
160+
// Attempt to get SkillConversationReference using deprecated method.
161+
// this catch should be removed once we remove the deprecated method.
162+
#pragma warning disable 618 // We need to use the deprecated method for backward compatibility.
163+
var conversationReference = await _conversationIdFactory.GetConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false);
164+
#pragma warning restore 618
165+
skillConversationReference = new SkillConversationReference
166+
{
167+
ConversationReference = conversationReference,
168+
OAuthScope = ChannelProvider != null && ChannelProvider.IsGovernment() ? GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope : AuthenticationConstants.ToChannelFromBotOAuthScope
169+
};
170+
}
154171

155-
if (conversationReference == null)
172+
if (skillConversationReference == null)
156173
{
157174
throw new KeyNotFoundException();
158175
}
159176

160-
var skillConversationReference = activity.GetConversationReference();
177+
var activityConversationReference = activity.GetConversationReference();
161178

162179
var callback = new BotCallbackHandler(async (turnContext, ct) =>
163180
{
164-
turnContext.TurnState.Add(SkillConversationReferenceKey, skillConversationReference);
181+
turnContext.TurnState.Add(SkillConversationReferenceKey, activityConversationReference);
165182

166-
activity.ApplyConversationReference(conversationReference);
183+
activity.ApplyConversationReference(skillConversationReference.ConversationReference);
167184

168185
turnContext.Activity.Id = replyToActivityId;
169186
switch (activity.Type)
170187
{
171188
case ActivityTypes.EndOfConversation:
172-
await _conversationIdIdFactory.DeleteConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false);
189+
await _conversationIdFactory.DeleteConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false);
173190
ApplyEoCToTurnContextActivity(turnContext, activity);
174191
await _bot.OnTurnAsync(turnContext, ct).ConfigureAwait(false);
175192
break;
@@ -183,7 +200,7 @@ private async Task<ResourceResponse> ProcessActivityAsync(ClaimsIdentity claimsI
183200
}
184201
});
185202

186-
await _adapter.ContinueConversationAsync(claimsIdentity, conversationReference, callback, cancellationToken).ConfigureAwait(false);
203+
await _adapter.ContinueConversationAsync(claimsIdentity, skillConversationReference.ConversationReference, skillConversationReference.OAuthScope, callback, cancellationToken).ConfigureAwait(false);
187204
return new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
188205
}
189206
}

libraries/integration/Microsoft.Bot.Builder.Integration.AspNet.Core/Skills/SkillHttpClient.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,45 @@ public SkillHttpClient(HttpClient httpClient, ICredentialProvider credentialProv
2525
_conversationIdFactory = conversationIdFactory;
2626
}
2727

28-
public async Task<InvokeResponse> PostActivityAsync(string fromBotId, BotFrameworkSkill toSkill, Uri callbackUrl, Activity activity, CancellationToken cancellationToken)
28+
/// <summary>
29+
/// Uses the SkillConversationIdFactory to create or retrieve a Skill Conversation Id, and sends the activity.
30+
/// </summary>
31+
/// <param name="originatingAudience">The oauth audience scope, used during token retrieval. (Either https://api.botframework.com or bot app id.)</param>
32+
/// <param name="fromBotId">The MicrosoftAppId of the bot sending the activity.</param>
33+
/// <param name="toSkill">The skill to create the conversation Id for.</param>
34+
/// <param name="callbackUrl">The callback Url for the skill host.</param>
35+
/// <param name="activity">The activity to send.</param>
36+
/// <param name="cancellationToken">A cancellation token.</param>
37+
/// <returns>Async task with invokeResponse.</returns>
38+
public async Task<InvokeResponse> PostActivityAsync(string originatingAudience, string fromBotId, BotFrameworkSkill toSkill, Uri callbackUrl, Activity activity, CancellationToken cancellationToken)
2939
{
30-
var skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(activity.GetConversationReference(), cancellationToken).ConfigureAwait(false);
40+
string skillConversationId;
41+
try
42+
{
43+
var options = new SkillConversationIdFactoryOptions
44+
{
45+
FromBotOAuthScope = originatingAudience,
46+
FromBotId = fromBotId,
47+
Activity = activity,
48+
BotFrameworkSkill = toSkill
49+
};
50+
skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(options, cancellationToken).ConfigureAwait(false);
51+
}
52+
catch (NotImplementedException)
53+
{
54+
// Attempt to create the ID using deprecated method.
55+
#pragma warning disable 618 // Keeping this for backward compat, this catch should be removed when the deprecated method is removed.
56+
skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(activity.GetConversationReference(), cancellationToken).ConfigureAwait(false);
57+
#pragma warning restore 618
58+
}
59+
3160
return await PostActivityAsync(fromBotId, toSkill.AppId, toSkill.SkillEndpoint, callbackUrl, skillConversationId, activity, cancellationToken).ConfigureAwait(false);
3261
}
62+
63+
public async Task<InvokeResponse> PostActivityAsync(string fromBotId, BotFrameworkSkill toSkill, Uri callbackUrl, Activity activity, CancellationToken cancellationToken)
64+
{
65+
var originatingAudience = ChannelProvider != null && ChannelProvider.IsGovernment() ? GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope : AuthenticationConstants.ToChannelFromBotOAuthScope;
66+
return await PostActivityAsync(originatingAudience, fromBotId, toSkill, callbackUrl, activity, cancellationToken).ConfigureAwait(false);
67+
}
3368
}
3469
}

tests/Microsoft.Bot.Builder.TestProtocol/MyConversationIdFactory.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,27 @@
77
using System.Threading;
88
using System.Threading.Tasks;
99
using Microsoft.Bot.Builder.Skills;
10-
using Microsoft.Bot.Schema;
11-
using Newtonsoft.Json;
1210

1311
namespace Microsoft.Bot.Builder.TestProtocol
1412
{
1513
public class MyConversationIdFactory : SkillConversationIdFactoryBase
1614
{
17-
private readonly ConcurrentDictionary<string, string> _conversationRefs = new ConcurrentDictionary<string, string>();
15+
private readonly ConcurrentDictionary<string, SkillConversationReference> _conversationRefs = new ConcurrentDictionary<string, SkillConversationReference>();
1816

19-
public override Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
17+
public override Task<string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken = default)
2018
{
21-
var crJson = JsonConvert.SerializeObject(conversationReference);
22-
var key = (conversationReference.Conversation.Id + conversationReference.ServiceUrl).GetHashCode().ToString(CultureInfo.InvariantCulture);
23-
_conversationRefs.GetOrAdd(key, crJson);
19+
var key = (options.Activity.Conversation.Id + options.Activity.ServiceUrl).GetHashCode().ToString(CultureInfo.InvariantCulture);
20+
_conversationRefs.GetOrAdd(key, new SkillConversationReference
21+
{
22+
ConversationReference = options.Activity.GetConversationReference(),
23+
OAuthScope = options.FromBotOAuthScope
24+
});
2425
return Task.FromResult(key);
2526
}
2627

27-
public override Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
28+
public override Task<SkillConversationReference> GetSkillConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
2829
{
29-
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(_conversationRefs[skillConversationId]);
30-
return Task.FromResult(conversationReference);
30+
return Task.FromResult(_conversationRefs[skillConversationId]);
3131
}
3232

3333
public override Task DeleteConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)