예제 #1
0
        protected virtual IActionResult EnsureSecureConnection(string receiverName, HttpRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            // Check to see if we have been configured to ignore this check
            if (ReceiverConfig.IsTrue(WebHookConstants.DisableHttpsCheckConfigurationKey))
            {
                return(null);
            }

            // Require HTTP unless request is local
            if (!request.IsLocal() && !request.IsHttps)
            {
                Logger.LogError(
                    500,
                    "The '{ReceiverName}' WebHook receiver requires HTTPS in order to be secure. " +
                    "Please register a WebHook URI of type '{SchemeName}'.",
                    receiverName,
                    Uri.UriSchemeHttps);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.Security_NoHttps,
                    receiverName,
                    Uri.UriSchemeHttps);
                var noHttps = WebHookResultUtilities.CreateErrorResult(message);

                return(noHttps);
            }

            return(null);
        }
        /// <summary>
        /// Decode the given <paramref name="hexEncodedValue"/>.
        /// </summary>
        /// <param name="hexEncodedValue">The hex-encoded <see cref="string"/>.</param>
        /// <param name="signatureHeaderName">
        /// The name of the HTTP header containing the <paramref name="hexEncodedValue"/>.
        /// </param>
        /// <param name="errorResult">
        /// Set to <see langword="null"/> if decoding is successful. Otherwise, an <see cref="IActionResult"/> that
        /// when executed will produce a response containing details about the problem.
        /// </param>
        /// <returns>
        /// If successful, the <see cref="byte"/> array containing the decoded hash. <see langword="null"/> if any
        /// issues occur.
        /// </returns>
        protected virtual byte[] GetDecodedHash(
            string hexEncodedValue,
            string signatureHeaderName,
            out IActionResult errorResult)
        {
            try
            {
                var decodedHash = EncodingUtilities.FromHex(hexEncodedValue);
                errorResult = null;

                return(decodedHash);
            }
            catch (Exception ex)
            {
                Logger.LogError(
                    401,
                    ex,
                    "The '{HeaderName}' header value is invalid. It must be a valid hex-encoded string.",
                    signatureHeaderName);
            }

            var message = string.Format(
                CultureInfo.CurrentCulture,
                Resources.Security_BadHeaderEncoding,
                signatureHeaderName);

            errorResult = WebHookResultUtilities.CreateErrorResult(message);

            return(null);
        }
        private IActionResult CreateUnsupportedMediaTypeResult(string message)
        {
            _logger.LogInformation(0, message);

            // ??? Should we instead provide CreateErrorResult(...) overloads with `int statusCode` parameters?
            var badMethod = WebHookResultUtilities.CreateErrorResult(message);

            badMethod.StatusCode = StatusCodes.Status415UnsupportedMediaType;

            return(badMethod);
        }
        protected virtual IActionResult CreateBadSignatureResult(string receiverName, string signatureHeaderName)
        {
            Logger.LogError(
                402,
                "The WebHook signature provided by the '{HeaderName}' header field does not match the value " +
                "expected by the '{ReceiverName}' receiver. WebHook request is invalid.",
                signatureHeaderName,
                receiverName);

            var message = string.Format(
                CultureInfo.CurrentCulture,
                Resources.VerifySignature_BadSignature,
                signatureHeaderName,
                receiverName);
            var badSignature = WebHookResultUtilities.CreateErrorResult(message);

            return(badSignature);
        }
예제 #5
0
        private async Task <IActionResult> GetChallengeResponse(
            WebHookGetRequest getMetadata,
            string receiverName,
            HttpRequest request,
            RouteData routeData)
        {
            // 1. Verify that we have the secret as an app setting.
            var secretKey = await GetReceiverConfig(
                request,
                routeData,
                receiverName,
                getMetadata.SecretKeyMinLength,
                getMetadata.SecretKeyMaxLength);

            if (secretKey == null)
            {
                return(new NotFoundResult());
            }

            // 2. Get the 'challenge' parameter from the request URI.
            var challenge = request.Query[getMetadata.ChallengeQueryParameterName];

            if (StringValues.IsNullOrEmpty(challenge))
            {
                Logger.LogError(
                    400,
                    "The WebHook verification request must contain a '{ParameterName}' query parameter.",
                    getMetadata.ChallengeQueryParameterName);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.General_MissingQueryParameter,
                    getMetadata.ChallengeQueryParameterName);
                var noChallenge = WebHookResultUtilities.CreateErrorResult(message);

                return(noChallenge);
            }

            // 3. Echo the challenge back to the caller.
            return(new ContentResult
            {
                Content = challenge,
            });
        }
예제 #6
0
        /// <inheritdoc />
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (!context.RouteData.TryGetReceiverName(out var receiverName))
            {
                // Not a WebHook request.
                return;
            }

            var bindingMetadata = _bindingMetadata.FirstOrDefault(metadata => metadata.IsApplicable(receiverName));

            if (bindingMetadata == null)
            {
                // Receiver has no additional parameters.
                return;
            }

            var request = context.HttpContext.Request;
            var headers = request.Headers;
            var query   = request.Query;

            for (var i = 0; i < bindingMetadata.Parameters.Count; i++)
            {
                var parameter = bindingMetadata.Parameters[i];
                if (parameter.IsRequired)
                {
                    var sourceName = parameter.SourceName;
                    var found      = parameter.IsQueryParameter ?
                                     VerifyQueryParameter(query, sourceName, receiverName, out var message) :
                                     VerifyHeader(headers, sourceName, receiverName, out message);
                    if (!found)
                    {
                        // Do not return after first error. Instead log about all issues.
                        context.Result = WebHookResultUtilities.CreateErrorResult(message);
                    }
                }
            }
        }
예제 #7
0
        private IActionResult CreateBadMethodResult(string methodName, string receiverName)
        {
            _logger.LogError(
                0,
                "The HTTP '{RequestMethod}' method is not supported by the '{ReceiverName}' WebHook receiver.",
                methodName,
                receiverName);

            var message = string.Format(
                CultureInfo.CurrentCulture,
                Resources.VerifyMethod_BadMethod,
                methodName,
                receiverName);

            // ??? Should we instead provide CreateErrorResult(...) overloads with `int statusCode` parameters?
            var badMethod = WebHookResultUtilities.CreateErrorResult(message);

            badMethod.StatusCode = StatusCodes.Status405MethodNotAllowed;

            return(badMethod);
        }
예제 #8
0
        /// <inheritdoc />
        public void OnException(ExceptionContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            // Apply to all requests matching an action with the WebHook route template.
            if (context.RouteData.TryGetWebHookReceiverName(out var receiverName))
            {
                _logger.LogError(
                    0,
                    context.Exception,
                    "WebHook receiver '{ReceiverName}' could not process WebHook due to error.",
                    receiverName);

                var result = WebHookResultUtilities.CreateErrorResult(
                    context.Exception,
                    includeErrorDetail: _hostingEnvironment.IsDevelopment());
                context.Result = result;
            }
        }
        protected virtual string GetRequestHeader(
            HttpRequest request,
            string headerName,
            out IActionResult errorResult)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }
            if (headerName == null)
            {
                throw new ArgumentNullException(nameof(headerName));
            }

            if (!request.Headers.TryGetValue(headerName, out var headers) || headers.Count != 1)
            {
                var headersCount = headers.Count;
                Logger.LogInformation(
                    400,
                    "Expecting exactly one '{HeaderName}' header field in the WebHook request but found " +
                    "{HeaderCount}. Please ensure the request contains exactly one '{HeaderName}' header field.",
                    headerName,
                    headersCount);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifySignature_BadHeader,
                    headerName,
                    headersCount);
                errorResult = WebHookResultUtilities.CreateErrorResult(message);

                return(null);
            }

            errorResult = null;

            return(headers);
        }
        /// <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));
            }

            var routeData = context.RouteData;

            if (!routeData.TryGetReceiverName(out var receiverName) || !IsApplicable(receiverName))
            {
                await next();

                return;
            }

            // 1. Confirm we were reached using HTTPS.
            var request     = context.HttpContext.Request;
            var errorResult = EnsureSecureConnection(receiverName, request);

            if (errorResult != null)
            {
                context.Result = errorResult;
                return;
            }

            // 2. Get XElement and SalesforceNotifications from the request body.
            var data = await ReadAsXmlAsync(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 = WebHookResultUtilities.CreateErrorResult(modelState);
                }

                return;
            }

            // Got a valid XML body. From this point on, all responses should contain XML.
            var notifications = new SalesforceNotifications(data);

            // 3. Ensure that the organization ID exists and matches the expected value.
            var organizationId = GetShortOrganizationId(notifications.OrganizationId);

            if (string.IsNullOrEmpty(organizationId))
            {
                Logger.LogError(
                    0,
                    "The HTTP request body did not contain a required '{PropertyName}' property.",
                    nameof(notifications.OrganizationId));

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifyOrganization_MissingValue,
                    nameof(notifications.OrganizationId));
                context.Result = await _resultCreator.GetFailedResultAsync(message);

                return;
            }

            var secret = await GetReceiverConfig(
                request,
                routeData,
                SalesforceConstants.ConfigurationName,
                SalesforceConstants.SecretKeyMinLength,
                SalesforceConstants.SecretKeyMaxLength);

            var secretKey = GetShortOrganizationId(secret);

            if (!SecretEqual(organizationId, secretKey))
            {
                Logger.LogError(
                    1,
                    "The '{PropertyName}' value provided in the HTTP request body did not match the expected value.",
                    nameof(notifications.OrganizationId));

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifyOrganization_BadValue,
                    nameof(notifications.OrganizationId));
                context.Result = await _resultCreator.GetFailedResultAsync(message);

                return;
            }

            // 4. Get the event name.
            var eventName = notifications.ActionId;

            if (string.IsNullOrEmpty(eventName))
            {
                Logger.LogError(
                    2,
                    "The HTTP request body did not contain a required '{PropertyName}' property.",
                    nameof(notifications.ActionId));

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifyOrganization_MissingValue,
                    nameof(notifications.ActionId));
                context.Result = await _resultCreator.GetFailedResultAsync(message);

                return;
            }

            // 5. Success. Provide request data and event name for model binding.
            routeData.Values[WebHookConstants.EventKeyName]            = eventName;
            context.HttpContext.Items[typeof(XElement)]                = data;
            context.HttpContext.Items[typeof(SalesforceNotifications)] = notifications;

            await next();
        }
        /// <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.TryGetReceiverName(out var receiverName) || !IsApplicable(receiverName))
            {
                await next();

                return;
            }

            // 2. Get JObject from the request body.
            var data = await ReadAsJsonAsync(context);

            if (data == null)
            {
                var modelState = context.ModelState;
                if (modelState.IsValid)
                {
                    // ReadAsJsonAsync 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 = WebHookResultUtilities.CreateErrorResult(modelState);
                }

                return;
            }

            // 3. Ensure the notification identifier exists.
            var notificationId = data.Value <string>(StripeConstants.NotificationIdPropertyName);

            if (string.IsNullOrEmpty(notificationId))
            {
                Logger.LogError(
                    0,
                    "The HTTP request body did not contain a required '{PropertyName}' property.",
                    StripeConstants.NotificationIdPropertyName);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifyNotification_MissingValue,
                    StripeConstants.NotificationIdPropertyName);
                context.Result = WebHookResultUtilities.CreateErrorResult(message);

                return;
            }

            // 4. Ensure the event name exists.
            // ??? Should this be optional? It's optional in `StripeWebHookReceiver` but Stripe docs imply otherwise.
            // ?? Should this be done later -- as in `StripeWebHookReceiver`? Seems useful even for test events.
            var eventName = data.Value <string>(StripeConstants.EventPropertyName);

            if (string.IsNullOrEmpty(eventName))
            {
                Logger.LogError(
                    1,
                    "The HTTP request body did not contain a required '{PropertyName}' property.",
                    StripeConstants.EventPropertyName);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifyNotification_MissingValue,
                    StripeConstants.EventPropertyName);
                context.Result = WebHookResultUtilities.CreateErrorResult(message);

                return;
            }

            // 5. Handle test events or get confirmed data.
            // `WebHookVerifyCodeFilter` has already handled the _useDirectWebHook verification.
            if (IsTestEvent(notificationId))
            {
                // Will short-circuit test events if !_passThroughTestEvents later, in StripeTestEventResponseFilter.
                if (_passThroughTestEvents)
                {
                    Logger.LogInformation(2, "Received a Stripe Test Event.");
                }
            }
            else if (!_useDirectWebHook)
            {
                // Callback to get the real data.
                data = await GetEventDataAsync(context.HttpContext.Request, routeData, notificationId);

                if (data == null)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.VerifyNotification_BadId,
                        notificationId);
                    context.Result = WebHookResultUtilities.CreateErrorResult(message);

                    return;
                }
            }

            // 6. Success. Provide request data and event name for model binding.
            routeData.Values[WebHookConstants.EventKeyName] = eventName;
            context.HttpContext.Items[typeof(JObject)]      = data;
            context.HttpContext.Items[typeof(StripeEvent)]  = data.ToObject <StripeEvent>();

            await next();
        }
예제 #12
0
        /// <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));
            }

            var routeData = context.RouteData;
            if (!routeData.TryGetWebHookReceiverName(out var receiverName) || !IsApplicable(receiverName))
            {
                await next();
                return;
            }

            // 1. Confirm we were reached using HTTPS.
            var request = context.HttpContext.Request;
            var errorResult = EnsureSecureConnection(receiverName, request);
            if (errorResult != null)
            {
                context.Result = errorResult;
                return;
            }

            // 2. Get IFormCollection from the request body.
            var data = await ReadAsFormDataAsync(context);
            if (data == null)
            {
                // ReadAsFormDataAsync returns null only when other filters will log and return errors about the same
                // conditions. Let those filters run.
                await next();
                return;
            }

            // 3. Ensure that the token exists and matches the expected value.
            string token = data[SlackConstants.TokenRequestFieldName];
            if (string.IsNullOrEmpty(token))
            {
                Logger.LogError(
                    0,
                    "The HTTP request body did not contain a required '{PropertyName}' property.",
                    SlackConstants.TokenRequestFieldName);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifyToken_MissingValue,
                    SlackConstants.TokenRequestFieldName);
                context.Result = WebHookResultUtilities.CreateErrorResult(message);

                return;
            }

            var secretKey = GetSecretKey(
                ReceiverName,
                routeData,
                SlackConstants.SecretKeyMinLength,
                SlackConstants.SecretKeyMaxLength);

            if (!SecretEqual(token, secretKey))
            {
                Logger.LogError(
                    1,
                    "The '{PropertyName}' value provided in the HTTP request body did not match the expected value.",
                    SlackConstants.TokenRequestFieldName);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifyToken_BadValue,
                    SlackConstants.TokenRequestFieldName);
                context.Result = WebHookResultUtilities.CreateErrorResult(message);

                return;
            }

            // 4. Get the event name and subtext.
            string eventName = data[SlackConstants.TriggerRequestFieldName];
            if (eventName != null)
            {
                // Trigger was supplied. Remove the trigger word to get subtext.
                string text = data[SlackConstants.TextRequestFieldName];
                routeData.Values[SlackConstants.SubtextRequestKeyName] = GetSubtext(eventName, text);
            }
            else if ((eventName = data[SlackConstants.CommandRequestFieldName]) != null)
            {
                // Command was supplied. No need to set subtext.
            }
            else
            {
                // Trigger and command were omitted. Set subtext to the full text (if any).
                eventName = data[SlackConstants.TextRequestFieldName];
                routeData.Values[SlackConstants.SubtextRequestKeyName] = eventName;
            }

            if (string.IsNullOrEmpty(eventName))
            {
                Logger.LogError(
                    2,
                    "The HTTP request body did not contain a required '{PropertyName1}', '{PropertyName2}', or " +
                    "'{PropertyName3}' property.",
                    SlackConstants.TriggerRequestFieldName,
                    SlackConstants.CommandRequestFieldName,
                    SlackConstants.TextRequestFieldName);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifyToken_MissingValues,
                    SlackConstants.TriggerRequestFieldName,
                    SlackConstants.CommandRequestFieldName,
                    SlackConstants.TextRequestFieldName);
                context.Result = WebHookResultUtilities.CreateErrorResult(message);

                return;
            }

            // 5. Success. Provide event name for model binding.
            routeData.Values[WebHookConstants.EventKeyName] = eventName;

            await next();
        }
        /// <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));
            }

            var routeData = context.RouteData;
            var request   = context.HttpContext.Request;

            if (routeData.TryGetReceiverName(out var receiverName) &&
                IsApplicable(receiverName) &&
                HttpMethods.IsPost(request.Method))
            {
                // 1. Get the expected hash from the signature headers.
                var header = GetRequestHeader(request, PusherConstants.SignatureHeaderName, out var errorResult);
                if (errorResult != null)
                {
                    context.Result = errorResult;
                    return;
                }

                var expectedHash = GetDecodedHash(header, PusherConstants.SignatureHeaderName, out errorResult);
                if (errorResult != null)
                {
                    context.Result = errorResult;
                    return;
                }

                // 2. Get the configured secret key.
                var lookupTable = await GetSecretLookupTable(request, routeData);

                if (lookupTable == null)
                {
                    context.Result = new NotFoundResult();
                    return;
                }

                var applicationKey = GetRequestHeader(request, PusherConstants.SignatureKeyHeaderName, out errorResult);
                if (errorResult != null)
                {
                    context.Result = errorResult;
                    return;
                }

                if (!lookupTable.TryGetValue(applicationKey, out var secretKey))
                {
                    Logger.LogError(
                        0,
                        "The '{HeaderName}' header value of '{HeaderValue}' is not recognized as a valid " +
                        "application key. Please ensure the correct application key / secret key pairs have " +
                        "been configured.",
                        PusherConstants.SignatureKeyHeaderName,
                        applicationKey);

                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.SignatureFilter_SecretNotFound,
                        PusherConstants.SignatureKeyHeaderName,
                        applicationKey);

                    context.Result = WebHookResultUtilities.CreateErrorResult(message);
                    return;
                }

                var secret = Encoding.UTF8.GetBytes(secretKey);

                // 3. Get the actual hash of the request body.
                var actualHash = await GetRequestBodyHash_SHA256(request, secret);

                // 4. Verify that the actual hash matches the expected hash.
                if (!SecretEqual(expectedHash, actualHash))
                {
                    // Log about the issue and short-circuit remainder of the pipeline.
                    errorResult = CreateBadSignatureResult(receiverName, PusherConstants.SignatureHeaderName);

                    context.Result = errorResult;
                    return;
                }
            }

            await next();
        }
예제 #14
0
        /// <inheritdoc />
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var routeData = context.RouteData;

            if (!routeData.TryGetWebHookReceiverName(out var receiverName))
            {
                // Not a WebHook request.
                return;
            }

            var bindingMetadata = _bindingMetadata.FirstOrDefault(metadata => metadata.IsApplicable(receiverName));

            if (bindingMetadata == null)
            {
                // Receiver has no additional parameters.
                return;
            }

            var request = context.HttpContext.Request;

            for (var i = 0; i < bindingMetadata.Parameters.Count; i++)
            {
                var parameter = bindingMetadata.Parameters[i];
                if (parameter.IsRequired)
                {
                    bool   found;
                    string message;
                    var    sourceName = parameter.SourceName;
                    switch (parameter.ParameterType)
                    {
                    case WebHookParameterType.Header:
                        found = VerifyHeader(request.Headers, sourceName, receiverName, out message);
                        break;

                    case WebHookParameterType.RouteValue:
                        found = VerifyRouteData(routeData, sourceName, receiverName, out message);
                        break;

                    case WebHookParameterType.QueryParameter:
                        found = VerifyQueryParameter(request.Query, sourceName, receiverName, out message);
                        break;

                    default:
                        message = string.Format(
                            CultureInfo.CurrentCulture,
                            Resources.General_InvalidEnumValue,
                            nameof(WebHookParameterType),
                            parameter.ParameterType);
                        throw new InvalidOperationException(message);
                    }

                    if (!found)
                    {
                        // Do not return after first error. Instead log about all issues.
                        context.Result = WebHookResultUtilities.CreateErrorResult(message);
                    }
                }
            }
        }
        /// <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));
            }

            var routeData = context.RouteData;
            var request   = context.HttpContext.Request;

            if (routeData.TryGetReceiverName(out var receiverName) &&
                IsApplicable(receiverName) &&
                HttpMethods.IsPost(request.Method))
            {
                // 1. Get the expected hash from the signature header.
                var header = GetRequestHeader(request, GitHubConstants.SignatureHeaderName, out var errorResult);
                if (errorResult != null)
                {
                    context.Result = errorResult;
                    return;
                }

                var values     = new TrimmingTokenizer(header, PairSeparators);
                var enumerator = values.GetEnumerator();
                enumerator.MoveNext();
                var headerKey = enumerator.Current;
                if (values.Count != 2 ||
                    !StringSegment.Equals(
                        headerKey,
                        GitHubConstants.SignatureHeaderKey,
                        StringComparison.OrdinalIgnoreCase))
                {
                    Logger.LogError(
                        1,
                        "Invalid '{HeaderName}' header value. Expecting a value of '{Key}={Value}'.",
                        GitHubConstants.SignatureHeaderName,
                        GitHubConstants.SignatureHeaderKey,
                        "<value>");

                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.SignatureFilter_BadHeaderValue,
                        GitHubConstants.SignatureHeaderName,
                        GitHubConstants.SignatureHeaderKey,
                        "<value>");
                    errorResult = WebHookResultUtilities.CreateErrorResult(message);

                    context.Result = errorResult;
                    return;
                }

                enumerator.MoveNext();
                var headerValue  = enumerator.Current.Value;
                var expectedHash = GetDecodedHash(headerValue, GitHubConstants.SignatureHeaderName, out errorResult);
                if (errorResult != null)
                {
                    context.Result = errorResult;
                    return;
                }

                // 2. Get the configured secret key.
                var secretKey = await GetReceiverConfig(
                    request,
                    routeData,
                    ReceiverName,
                    GitHubConstants.SecretKeyMinLength,
                    GitHubConstants.SecretKeyMaxLength);

                if (secretKey == null)
                {
                    context.Result = new NotFoundResult();
                    return;
                }

                var secret = Encoding.UTF8.GetBytes(secretKey);

                // 3. Get the actual hash of the request body.
                var actualHash = await GetRequestBodyHash_SHA1(request, secret);

                // 4. Verify that the actual hash matches the expected hash.
                if (!SecretEqual(expectedHash, actualHash))
                {
                    // Log about the issue and short-circuit remainder of the pipeline.
                    errorResult = CreateBadSignatureResult(receiverName, GitHubConstants.SignatureHeaderName);

                    context.Result = errorResult;
                    return;
                }
            }

            await next();
        }
예제 #16
0
        protected virtual async Task <IActionResult> EnsureValidCode(
            HttpRequest request,
            RouteData routeData,
            string receiverName)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }
            if (routeData == null)
            {
                throw new ArgumentNullException(nameof(routeData));
            }
            if (receiverName == null)
            {
                throw new ArgumentNullException(nameof(receiverName));
            }

            var result = EnsureSecureConnection(receiverName, request);

            if (result != null)
            {
                return(result);
            }

            var code = request.Query[WebHookConstants.CodeQueryParameterName];

            if (StringValues.IsNullOrEmpty(code))
            {
                Logger.LogError(
                    400,
                    "The WebHook verification request must contain a '{ParameterName}' query parameter.",
                    WebHookConstants.CodeQueryParameterName);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.General_MissingQueryParameter,
                    WebHookConstants.CodeQueryParameterName);
                var noCode = WebHookResultUtilities.CreateErrorResult(message);

                return(noCode);
            }

            var secretKey = await GetReceiverConfig(
                request,
                routeData,
                receiverName,
                WebHookConstants.CodeParameterMinLength,
                WebHookConstants.CodeParameterMaxLength);

            if (secretKey == null)
            {
                return(new NotFoundResult());
            }

            if (!SecretEqual(code, secretKey))
            {
                Logger.LogError(
                    401,
                    "The '{ParameterName}' query parameter provided in the HTTP request did not match the " +
                    "expected value.",
                    WebHookConstants.CodeQueryParameterName);

                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.VerifyCode_BadCode,
                    WebHookConstants.CodeQueryParameterName);
                var invalidCode = WebHookResultUtilities.CreateErrorResult(message);

                return(invalidCode);
            }

            return(null);
        }