-
Notifications
You must be signed in to change notification settings - Fork 175
Expand file tree
/
Copy pathTokenExchangeSkillHandler.cs
More file actions
158 lines (138 loc) · 7.69 KB
/
TokenExchangeSkillHandler.cs
File metadata and controls
158 lines (138 loc) · 7.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
using System;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Skills;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
namespace Bot.Builder.Community.Components.TokenExchangeSkillHandler
{
#pragma warning disable CA1812 // Avoid uninstantiated internal
internal class TokenExchangeSkillHandler : CloudSkillHandler
#pragma warning restore CA1812 // Avoid uninstantiated internal
{
private readonly BotAdapter _adapter;
private readonly SkillsConfiguration _skillsConfig;
private readonly string _botId;
private readonly string _connectionName;
private readonly SkillConversationIdFactoryBase _conversationIdFactory;
private readonly BotFrameworkAuthentication _botAuth;
private readonly ILogger _logger;
public TokenExchangeSkillHandler(
BotAdapter adapter,
IBot bot,
IConfiguration configuration,
SkillConversationIdFactoryBase conversationIdFactory,
BotFrameworkAuthentication authConfig,
SkillsConfiguration skillsConfig = default,
ILogger logger = default)
: base(adapter, bot, conversationIdFactory, authConfig, logger)
{
_adapter = adapter;
_botAuth = authConfig;
_conversationIdFactory = conversationIdFactory;
_skillsConfig = skillsConfig ?? new SkillsConfiguration(configuration);
_botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
_logger = logger;
var settings = configuration.GetSection("Bot.Builder.Community.Components.TokenExchangeSkillHandler")?.Get<ComponentSettings>() ?? new ComponentSettings();
_connectionName = settings.TokenExchangeConnectionName ?? configuration.GetSection("tokenExchangeConnectionName")?.Value;
}
protected override async Task<ResourceResponse> OnSendToConversationAsync(ClaimsIdentity claimsIdentity, string conversationId, Activity activity, CancellationToken cancellationToken = default(CancellationToken))
{
if (await InterceptOAuthCardsAsync(claimsIdentity, activity).ConfigureAwait(false))
{
return new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
}
return await base.OnSendToConversationAsync(claimsIdentity, conversationId, activity, cancellationToken).ConfigureAwait(false);
}
protected override async Task<ResourceResponse> OnReplyToActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default(CancellationToken))
{
if (await InterceptOAuthCardsAsync(claimsIdentity, activity).ConfigureAwait(false))
{
return new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
}
return await base.OnReplyToActivityAsync(claimsIdentity, conversationId, activityId, activity, cancellationToken).ConfigureAwait(false);
}
private BotFrameworkSkill GetCallingSkill(ClaimsIdentity claimsIdentity)
{
var appId = JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims);
if (string.IsNullOrWhiteSpace(appId))
{
return null;
}
return _skillsConfig.Skills.Values.FirstOrDefault(s => string.Equals(s.AppId, appId, StringComparison.OrdinalIgnoreCase));
}
private async Task<bool> InterceptOAuthCardsAsync(ClaimsIdentity claimsIdentity, Activity activity)
{
var oauthCardAttachment = activity.Attachments?.FirstOrDefault(a => a?.ContentType == OAuthCard.ContentType);
if (oauthCardAttachment != null)
{
var targetSkill = GetCallingSkill(claimsIdentity);
if (targetSkill != null)
{
var oauthCard = ((JObject)oauthCardAttachment.Content).ToObject<OAuthCard>();
if (!string.IsNullOrWhiteSpace(oauthCard?.TokenExchangeResource?.Uri))
{
using (var context = new TurnContext(_adapter, activity))
{
context.TurnState.Add<IIdentity>("BotIdentity", claimsIdentity);
// AAD token exchange
try
{
var tokenClient = await _botAuth.CreateUserTokenClientAsync(claimsIdentity, CancellationToken.None).ConfigureAwait(false);
var result = await tokenClient.ExchangeTokenAsync(
activity.Recipient.Id,
_connectionName,
activity.ChannelId,
new TokenExchangeRequest() { Uri = oauthCard.TokenExchangeResource.Uri },
CancellationToken.None).ConfigureAwait(false);
if (!string.IsNullOrEmpty(result?.Token))
{
// If token above is null, then SSO has failed and hence we return false.
// If not, send an invoke to the skill with the token.
return await SendTokenExchangeInvokeToSkillAsync(activity, oauthCard.TokenExchangeResource.Id, result.Token, oauthCard.ConnectionName, targetSkill, default).ConfigureAwait(false);
}
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
#pragma warning restore CA1031 // Do not catch general exception types
{
return false;
}
return false;
}
}
}
}
return false;
}
private async Task<bool> SendTokenExchangeInvokeToSkillAsync(Activity incomingActivity, string id, string token, string connectionName, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
{
var activity = incomingActivity.CreateReply() as Activity;
activity.Type = ActivityTypes.Invoke;
activity.Name = SignInConstants.TokenExchangeOperationName;
activity.Value = new TokenExchangeInvokeRequest()
{
Id = id,
Token = token,
ConnectionName = connectionName,
};
var skillConversationReference = await _conversationIdFactory.GetSkillConversationReferenceAsync(incomingActivity.Conversation.Id, cancellationToken).ConfigureAwait(false);
activity.Conversation = skillConversationReference.ConversationReference.Conversation;
// route the activity to the skill
using (var client = _botAuth.CreateBotFrameworkClient())
{
var response = await client.PostActivityAsync(_botId, targetSkill.AppId, targetSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, incomingActivity.Conversation.Id, activity, cancellationToken).ConfigureAwait(false);
// Check response status: true if success, false if failure
return response.Status >= 200 && response.Status <= 299;
}
}
}
}