/// <inheritdoc /> public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (next == null) { throw new ArgumentNullException(nameof(next)); } // 1. Confirm this filter applies. var routeData = context.RouteData; if (!routeData.TryGetWebHookReceiverName(out var receiverName) || !IsApplicable(receiverName)) { await next(); return; } // 2. Confirm we were reached using HTTPS. var request = context.HttpContext.Request; var errorResult = EnsureSecureConnection(receiverName, request); if (errorResult != null) { context.Result = errorResult; return; } // 3. Get XElement from the request body. var data = await _requestReader.ReadBodyAsync <XElement>(context); if (data == 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; } // 4. Ensure the organization ID exists and matches the expected value. var organizationIds = ObjectPathUtilities.GetStringValues(data, SalesforceConstants.OrganizationIdPath); if (StringValues.IsNullOrEmpty(organizationIds)) { Logger.LogError( 0, "The HTTP request body did not contain a required '{XPath}' element.", SalesforceConstants.OrganizationIdPath); var message = string.Format( CultureInfo.CurrentCulture, Resources.VerifyOrganization_MissingValue, SalesforceConstants.OrganizationIdPath); context.Result = await _resultCreator.GetFailedResultAsync(message); return; } var secret = GetSecretKey( ReceiverName, routeData, SalesforceConstants.SecretKeyMinLength, SalesforceConstants.SecretKeyMaxLength); var organizationId = GetShortOrganizationId(organizationIds[0]); var secretKey = GetShortOrganizationId(secret); if (!SecretEqual(organizationId, secretKey)) { Logger.LogError( 1, "The '{XPath}' value provided in the HTTP request body did not match the expected value.", SalesforceConstants.OrganizationIdPath); var message = string.Format( CultureInfo.CurrentCulture, Resources.VerifyOrganization_BadValue, SalesforceConstants.OrganizationIdPath); context.Result = await _resultCreator.GetFailedResultAsync(message); return; } // 5. Get the event name. var eventNames = ObjectPathUtilities.GetStringValues(data, SalesforceConstants.EventNamePath); if (StringValues.IsNullOrEmpty(eventNames)) { Logger.LogError( 2, "The HTTP request body did not contain a required '{XPath}' element.", SalesforceConstants.EventNamePath); var message = string.Format( CultureInfo.CurrentCulture, Resources.VerifyOrganization_MissingValue, SalesforceConstants.EventNamePath); context.Result = await _resultCreator.GetFailedResultAsync(message); return; } // 6. Success. Provide event name for model binding. routeData.SetWebHookEventNames(eventNames); await next(); }
/// <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; if (!routeData.TryGetWebHookReceiverName(out var receiverName)) { await next(); return; } var eventFromBodyMetadata = _eventFromBodyMetadata .FirstOrDefault(metadata => metadata.IsApplicable(receiverName)); if (eventFromBodyMetadata == null) { await next(); return; } // Determine the applicable WebhookBodyType i.e. how to read the request body. // WebHookReceiverExistsConstraint confirms the IWebHookBodyTypeMetadataService implementation exists. var bodyTypeMetadata = _bodyTypeMetadata.First(metadata => metadata.IsApplicable(receiverName)); // 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) { _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(); }