BotAuthentication.cs
1 using Newtonsoft.Json.Linq;
2 using System;
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Linq;
6 using System.Net;
7 using System.Net.Http;
8 using System.Security.Claims;
9 using System.Threading;
10 using System.Threading.Tasks;
11 using System.Web;
12 using System.Web.Http.Controllers;
13 using System.Web.Http.Filters;
14 
15 namespace Microsoft.Bot.Connector
16 {
17  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
18  public class BotAuthentication : ActionFilterAttribute
19  {
26  public string MicrosoftAppId { get; set; }
27 
34  public string MicrosoftAppPassword { get; set; }
35 
42  public string MicrosoftAppIdSettingName { get; set; }
43 
50  public string MicrosoftAppPasswordSettingName { get; set; }
51 
52  public bool DisableEmulatorTokens { get; set; }
53 
57  public Type CredentialProviderType { get; set; }
58 
59  public virtual string OpenIdConfigurationUrl { get; set; } = JwtConfig.ToBotFromChannelOpenIdMetadataUrl;
60 
61 
62 
63  public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
64  {
65  var provider = this.GetCredentialProvider();
66  var botAuthenticator = new BotAuthenticator(provider, OpenIdConfigurationUrl, DisableEmulatorTokens);
67  var identityToken = await botAuthenticator.TryAuthenticateAsync(actionContext.Request, cancellationToken);
68 
69  // the request is not authenticated, fail with 401.
70  if (!identityToken.Authenticated)
71  {
72  actionContext.Response = BotAuthenticator.GenerateUnauthorizedResponse(actionContext.Request);
73  return;
74  }
75 
76  botAuthenticator.TrustServiceUrls(identityToken, GetActivities(actionContext));
77  await base.OnActionExecutingAsync(actionContext, cancellationToken);
78  }
79 
80  private IList<Activity> GetActivities(HttpActionContext actionContext)
81  {
82  var activties = actionContext.ActionArguments.Select(t => t.Value).OfType<Activity>().ToList();
83  if (activties.Any())
84  {
85  return activties;
86  }
87  else
88  {
89  var objects =
90  actionContext.ActionArguments.Where(t => t.Value is JObject || t.Value is JArray)
91  .Select(t => t.Value).ToArray();
92  if (objects.Any())
93  {
94  activties = new List<Activity>();
95  foreach (var obj in objects)
96  {
97  activties.AddRange((obj is JObject) ? new Activity[] { ((JObject)obj).ToObject<Activity>() } : ((JArray)obj).ToObject<Activity[]>());
98  }
99  }
100  }
101  return activties;
102  }
103 
104  private ICredentialProvider GetCredentialProvider()
105  {
106  ICredentialProvider credentialProvider = null;
107  if (CredentialProviderType != null)
108  {
109  // if we have a credentialprovider type
110  credentialProvider = Activator.CreateInstance(CredentialProviderType) as ICredentialProvider;
111  if (credentialProvider == null)
112  throw new ArgumentNullException($"The CredentialProviderType {CredentialProviderType.Name} couldn't be instantiated with no params or doesn't implement ICredentialProvider");
113  }
114  else if (MicrosoftAppId != null && MicrosoftAppPassword != null)
115  {
116  // if we have raw values
117  credentialProvider = new StaticCredentialProvider(MicrosoftAppId, MicrosoftAppPassword);
118 
119  }
120  else
121  {
122  // if we have setting name, or there is no parameters at all default to default setting name
123  credentialProvider = new SettingsCredentialProvider(MicrosoftAppIdSettingName, MicrosoftAppPasswordSettingName);
124  }
125  return credentialProvider;
126  }
127  }
128 
129  public sealed class BotAuthenticator
130  {
131  private readonly ICredentialProvider credentialProvider;
132  private readonly string openIdConfigurationUrl;
133  private readonly bool disableEmulatorTokens;
134 
144  public BotAuthenticator(string microsoftAppId, string microsoftAppPassword)
145  {
146  this.credentialProvider = new StaticCredentialProvider(microsoftAppId, microsoftAppPassword);
147  this.openIdConfigurationUrl = JwtConfig.ToBotFromChannelOpenIdMetadataUrl;
148  // by default Authenticator is not disabling emulator tokens
149  this.disableEmulatorTokens = false;
150  }
151 
152  public BotAuthenticator(ICredentialProvider credentialProvider, string openIdConfigurationUrl,
153  bool disableEmulatorTokens)
154  {
155  if (credentialProvider == null)
156  {
157  throw new ArgumentNullException(nameof(credentialProvider));
158  }
159  this.credentialProvider = credentialProvider;
160  this.openIdConfigurationUrl = openIdConfigurationUrl;
161  this.disableEmulatorTokens = disableEmulatorTokens;
162 
163  }
164 
173  public async Task<bool> TryAuthenticateAsync(HttpRequestMessage request, IEnumerable<IActivity> activities,
174  CancellationToken token)
175  {
176  var identityToken = await this.TryAuthenticateAsync(request, token);
177  TrustServiceUrls(identityToken, activities);
178  return identityToken.Authenticated;
179  }
180 
186  public static HttpResponseMessage GenerateUnauthorizedResponse(HttpRequestMessage request)
187  {
188  string host = request.RequestUri.DnsSafeHost;
189  var response = request.CreateResponse(HttpStatusCode.Unauthorized);
190  response.Headers.Add("WWW-Authenticate", string.Format("Bearer realm=\"{0}\"", host));
191  return response;
192  }
193 
194  internal void TrustServiceUrls(IdentityToken identityToken, IEnumerable<IActivity> activities)
195  {
196  // add the service url to the list of trusted urls only if the JwtToken
197  // is valid and identity is not null
198  if (identityToken.Authenticated && identityToken.Identity != null)
199  {
200  if (activities.Any())
201  {
202  foreach (var activity in activities)
203  {
204  MicrosoftAppCredentials.TrustServiceUrl(activity?.ServiceUrl);
205  }
206  }
207  else
208  {
209  Trace.TraceWarning("No ServiceUrls added to trusted list");
210  }
211  }
212  }
213 
214  internal async Task<IdentityToken> TryAuthenticateAsync(HttpRequestMessage request,
215  CancellationToken token)
216  {
217  // then auth is disabled
218  if (await this.credentialProvider.IsAuthenticationDisabledAsync())
219  {
220  return new IdentityToken(true, null);
221  }
222 
223  ClaimsIdentity identity = null;
224  var tokenExtractor = GetTokenExtractor();
225  identity = await tokenExtractor.GetIdentityAsync(request);
226 
227  // No identity? If we're allowed to, fall back to MSA
228  // This code path is used by the emulator
229  if (identity == null && !this.disableEmulatorTokens)
230  {
232  identity = await tokenExtractor.GetIdentityAsync(request);
233  }
234 
235  if (identity != null)
236  {
237  var appId = tokenExtractor.GetAppIdFromClaimsIdentity(identity);
238  if (await credentialProvider.IsValidAppIdAsync(appId) == false) // keep context
239  {
240  // not valid appid, drop the identity
241  identity = null;
242  }
243  else
244  {
245  var password = await credentialProvider.GetAppPasswordAsync(appId); // Keep context
246  if (password != null)
247  {
248  // add password as claim so that it is part of ClaimsIdentity and accessible by ConnectorClient()
249  identity.AddClaim(new Claim(ClaimsIdentityEx.AppPasswordClaim, password));
250  }
251  }
252  }
253 
254  if (identity != null)
255  {
256  Thread.CurrentPrincipal = new ClaimsPrincipal(identity);
257 
258  // Inside of ASP.NET this is required
259  if (HttpContext.Current != null)
260  HttpContext.Current.User = Thread.CurrentPrincipal;
261 
262  return new IdentityToken(true, identity);
263  }
264 
265  return new IdentityToken(false, null);
266  }
267 
268  private JwtTokenExtractor GetTokenExtractor()
269  {
270  var parameters = JwtConfig.GetToBotFromChannelTokenValidationParameters((audiences, securityToken, validationParameters) => true);
271  return new JwtTokenExtractor(parameters, this.openIdConfigurationUrl);
272  }
273 
274  }
275 
276  internal sealed class IdentityToken
277  {
278  public readonly bool Authenticated;
279  public readonly ClaimsIdentity Identity;
280 
281  public IdentityToken(bool authenticated, ClaimsIdentity identity)
282  {
283  this.Authenticated = authenticated;
284  this.Identity = identity;
285  }
286  }
287 }
BotAuthenticator(ICredentialProvider credentialProvider, string openIdConfigurationUrl, bool disableEmulatorTokens)
async Task< bool > TryAuthenticateAsync(HttpRequestMessage request, IEnumerable< IActivity > activities, CancellationToken token)
Authenticates the incoming request and add the IActivity.ServiceUrl for each activities to MicrosoftA...
const string ToBotFromChannelOpenIdMetadataUrl
TO BOT FROM CHANNEL: OpenID metadata document for tokens coming from MSA
Definition: JwtConfig.cs:15
Credential provider which uses config settings to lookup appId and password
Configuration for JWT tokens
Definition: JwtConfig.cs:10
static TokenValidationParameters GetToBotFromChannelTokenValidationParameters(AudienceValidator validator)
TO BOT FROM CHANNEL: Token validation parameters when connecting to a bot
Definition: JwtConfig.cs:20
override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
const string ToBotFromMSAOpenIdMetadataUrl
TO BOT FROM MSA: OpenID metadata document for tokens coming from MSA
Definition: JwtConfig.cs:40
An Activity is the basic communication type for the Bot Framework 3.0 protocol
Definition: ActivityEx.cs:17
static readonly TokenValidationParameters ToBotFromMSATokenValidationParameters
TO BOT FROM MSA: Token validation parameters when connecting to a channel
Definition: JwtConfig.cs:48
static HttpResponseMessage GenerateUnauthorizedResponse(HttpRequestMessage request)
Generates HttpStatusCode.Unauthorized response for the request.
Static credential provider which has the appid and password static
BotAuthenticator(string microsoftAppId, string microsoftAppPassword)
Creates an instance of bot authenticator.