/// <inheritdoc /> public bool Accept(ActionConstraintContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (!context.RouteContext.RouteData.TryGetWebHookReceiverName( context.CurrentCandidate.Action, out var receiverName)) { return(false); } if (_bodyTypeMetadata == null) { if (_metadataProvider.GetBodyTypeMetadata(receiverName) == null) { // Received a request for (say) https://{host}/api/webhooks/incoming/mine but the "mine" receiver // is not configured. But, probably not a misconfiguration in this application. // WebHookMetadataProvier detects "incomplete" receivers i.e. those with some metadata // services but lacking an IWebHookBodyTypeMetadataService implementation. return(false); } } else { if (!_bodyTypeMetadata.IsApplicable(receiverName)) { // Received a request for (say) https://{host}/api/webhooks/incoming/their but this action is // configured for the "mine" receiver. return(false); } } context.RouteContext.RouteData.Values[WebHookConstants.ReceiverExistsKeyName] = true; return(true); }
/// <inheritdoc /> public void OnResourceExecuting(ResourceExecutingContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // bodyTypeMetadata will never end up null. WebHookActionModelPropertyProvider and // WebHookEventNameConstraint confirms the IWebHookBodyTypeMetadataService implementation exists. var bodyTypeMetadata = _bodyTypeMetadata; if (bodyTypeMetadata == null) { if (!context.RouteData.TryGetWebHookReceiverName(out var requestReceiverName)) { return; } bodyTypeMetadata = _metadataProvider.GetBodyTypeMetadata(requestReceiverName); } var receiverName = bodyTypeMetadata.ReceiverName; var request = context.HttpContext.Request; var contentType = request.GetTypedHeaders().ContentType; switch (bodyTypeMetadata.BodyType) { case WebHookBodyType.Form: if (!request.HasFormContentType) { _logger.LogWarning( 0, "The '{ReceiverName}' WebHook receiver does not support content type '{ContentType}'. " + "The WebHook request must contain an entity body formatted as HTML form URL-encoded data.", receiverName, contentType); var message = string.Format( CultureInfo.CurrentCulture, Resources.VerifyBody_NoFormData, receiverName, contentType); context.Result = new BadRequestObjectResult(message) { StatusCode = StatusCodes.Status415UnsupportedMediaType }; } break; case WebHookBodyType.Json: if (!IsJson(contentType)) { _logger.LogWarning( 1, "The '{ReceiverName}' WebHook receiver does not support content type '{ContentType}'. " + "The WebHook request must contain an entity body formatted as JSON.", receiverName, contentType); var message = string.Format( CultureInfo.CurrentCulture, Resources.VerifyBody_NoJson, receiverName, contentType); context.Result = new BadRequestObjectResult(message) { StatusCode = StatusCodes.Status415UnsupportedMediaType }; } break; case WebHookBodyType.Xml: if (!IsXml(contentType)) { _logger.LogWarning( 2, "The '{ReceiverName}' WebHook receiver does not support content type '{ContentType}'. " + "The WebHook request must contain an entity body formatted as XML.", receiverName, contentType); var message = string.Format( CultureInfo.CurrentCulture, Resources.VerifyBody_NoXml, receiverName, contentType); context.Result = new BadRequestObjectResult(message) { StatusCode = StatusCodes.Status415UnsupportedMediaType }; } break; default: { var message = string.Format( CultureInfo.CurrentCulture, Resources.General_InvalidEnumValue, typeof(WebHookBodyType), bodyTypeMetadata.BodyType); throw new InvalidOperationException(message); } } }
/// <inheritdoc /> public virtual async Task OnResourceExecutionAsync( ResourceExecutingContext context, ResourceExecutionDelegate next) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (next == null) { throw new ArgumentNullException(nameof(next)); } var routeData = context.RouteData; var bodyTypeMetadata = _bodyTypeMetadata; var eventFromBodyMetadata = _eventFromBodyMetadata; if (bodyTypeMetadata == null) { if (!routeData.TryGetWebHookReceiverName(out var receiverName)) { await next(); return; } bodyTypeMetadata = _metadataProvider.GetBodyTypeMetadata(receiverName); eventFromBodyMetadata = _metadataProvider.GetEventFromBodyMetadata(receiverName); if (eventFromBodyMetadata == null) { await next(); return; } } // No need to double-check the request's Content-Type. WebHookVerifyBodyTypeFilter would have // short-circuited the request if unsupported. StringValues eventNames; switch (bodyTypeMetadata.BodyType) { case WebHookBodyType.Form: var form = await _requestReader.ReadAsFormDataAsync(context); if (form == null) { // ReadAsFormDataAsync returns null only when other filters will log and return errors // about the same conditions. Let those filters run. await next(); return; } eventNames = form[eventFromBodyMetadata.BodyPropertyPath]; break; case WebHookBodyType.Json: var json = await _requestReader.ReadBodyAsync <JContainer>(context); if (json == null) { var modelState = context.ModelState; if (modelState.IsValid) { // ReadAsJContainerAsync returns null when model state is valid only when other filters // will log and return errors about the same conditions. Let those filters run. await next(); } else { context.Result = new BadRequestObjectResult(modelState); } return; } eventNames = ObjectPathUtilities.GetStringValues(json, eventFromBodyMetadata.BodyPropertyPath); break; case WebHookBodyType.Xml: var xml = await _requestReader.ReadBodyAsync <XElement>(context); if (xml == null) { var modelState = context.ModelState; if (modelState.IsValid) { // ReadAsXmlAsync returns null when model state is valid only when other filters will log // and return errors about the same conditions. Let those filters run. await next(); } else { context.Result = new BadRequestObjectResult(modelState); } return; } eventNames = ObjectPathUtilities.GetStringValues(xml, eventFromBodyMetadata.BodyPropertyPath); break; default: var message = string.Format( CultureInfo.CurrentCulture, Resources.General_InvalidEnumValue, typeof(WebHookBodyType), bodyTypeMetadata.BodyType); throw new InvalidOperationException(message); } if (StringValues.IsNullOrEmpty(eventNames) && !eventFromBodyMetadata.AllowMissing) { var receiverName = bodyTypeMetadata.ReceiverName; _logger.LogWarning( 0, "A '{ReceiverName}' WebHook request must contain a match for '{BodyPropertyPath}' in the HTTP " + "request entity body indicating the type or types of event.", receiverName, eventFromBodyMetadata.BodyPropertyPath); var message = string.Format( CultureInfo.CurrentCulture, Resources.EventMapper_NoBodyProperty, receiverName, eventFromBodyMetadata.BodyPropertyPath); context.Result = new BadRequestObjectResult(message); return; } routeData.SetWebHookEventNames(eventNames); await next(); }