Пример #1
0
        private async Task <IActionResult> HandleIntentVerificationAsync(WebSubSubscription subscription, IQueryCollection requestQuery, IServiceProvider requestServices)
        {
            IActionResult intentVerificationResult = new NotFoundResult();

            if (subscription != null)
            {
                IWebSubSubscriptionsStore   subscriptionsStore   = requestServices.GetRequiredService <IWebSubSubscriptionsStore>();
                IWebSubSubscriptionsService subscriptionsService = requestServices.GetService <IWebSubSubscriptionsService>();

                switch (requestQuery[WebSubConstants.MODE_QUERY_PARAMETER_NAME])
                {
                case WebSubConstants.MODE_DENIED:
                    intentVerificationResult = await HandleSubscribeIntentDenyAsync(subscription, subscriptionsStore, subscriptionsService, requestQuery);

                    break;

                case WebSubConstants.MODE_SUBSCRIBE:
                    intentVerificationResult = await HandleSubscribeIntentVerificationAsync(subscription, subscriptionsStore, subscriptionsService, requestQuery);

                    break;

                case WebSubConstants.MODE_UNSUBSCRIBE:
                    intentVerificationResult = await HandleUnsubscribeIntentVerificationAsync(subscription, subscriptionsStore, subscriptionsService, requestQuery);

                    break;

                default:
                    intentVerificationResult = HandleBadRequest($"A '{WebSubConstants.ReceiverName}' WebHook intent verification request contains unknown '{WebSubConstants.MODE_QUERY_PARAMETER_NAME}' query parameter value.");
                    break;
                }
            }

            return(intentVerificationResult);
        }
Пример #2
0
        private static Task <HttpResponseMessage> HandleIntentVerificationAsync(WebSubSubscription subscription, HttpRequestMessage request)
        {
            Dictionary <string, string> requestQuery = request.GetQueryNameValuePairs().ToDictionary(nameValuePair => nameValuePair.Key, nameValuePair => nameValuePair.Value, StringComparer.OrdinalIgnoreCase);

            if (requestQuery.TryGetValue(MODE_QUERY_PARAMETER_NAME, out string mode))
            {
                switch (mode)
                {
                case MODE_DENIED:
                    return(HandleSubscribeIntentDenyAsync(request, requestQuery, subscription));

                case MODE_SUBSCRIBE:
                    return(HandleSubscribeIntentVerificationAsync(request, requestQuery, subscription));

                case MODE_UNSUBSCRIBE:
                    return(HandleUnsubscribeIntentVerificationAsync(request, requestQuery, subscription));

                default:
                    return(Task.FromResult(HandleBadRequest(request, $"A '{ReceiverName}' WebHook intent verification request contains unknown '{MODE_QUERY_PARAMETER_NAME}' query parameter value.")));
                }
            }
            else
            {
                return(Task.FromResult(HandleMissingIntentVerificationParameter(request, MODE_QUERY_PARAMETER_NAME)));
            }
        }
Пример #3
0
        private async Task <bool> VerifyUnsubscribeIntentAsync(WebSubSubscription subscription, IWebSubSubscriptionsStore subscriptionsStore, IWebSubSubscriptionsService subscriptionsService, string topic)
        {
            bool verified = false;

            if (subscription.State == WebSubSubscriptionState.UnsubscribeRequested)
            {
                if (subscription.TopicUrl != topic)
                {
                    if (subscriptionsService != null)
                    {
                        await subscriptionsService.OnInvalidUnsubscribeIntentVerificationAsync(subscription, subscriptionsStore);
                    }
                }
                else if ((subscriptionsService == null) || (await subscriptionsService.OnUnsubscribeIntentVerificationAsync(subscription, subscriptionsStore)))
                {
                    subscription.State = WebSubSubscriptionState.UnsubscribeValidated;
                    subscription.VerificationRequestTimeStampUtc = DateTime.UtcNow;
                    await subscriptionsStore.UpdateAsync(subscription);

                    verified = true;
                }
            }

            return(verified);
        }
Пример #4
0
        private async Task <IActionResult> HandleUnsubscribeIntentVerificationAsync(WebSubSubscription subscription, IWebSubSubscriptionsStore subscriptionsStore, IWebSubSubscriptionsService subscriptionsService, IQueryCollection requestQuery)
        {
            StringValues topicValues = requestQuery[WebSubConstants.TOPIC_QUERY_PARAMETER_NAME];

            if (StringValues.IsNullOrEmpty(topicValues))
            {
                return(HandleMissingIntentVerificationParameter(WebSubConstants.TOPIC_QUERY_PARAMETER_NAME));
            }

            StringValues challengeValues = requestQuery[WebSubConstants.INTENT_VERIFICATION_CHALLENGE_QUERY_PARAMETER_NAME];

            if (StringValues.IsNullOrEmpty(challengeValues))
            {
                return(HandleMissingIntentVerificationParameter(WebSubConstants.INTENT_VERIFICATION_CHALLENGE_QUERY_PARAMETER_NAME));
            }

            if (await VerifyUnsubscribeIntentAsync(subscription, subscriptionsStore, subscriptionsService, topicValues))
            {
                _logger.LogInformation("Received an unsubscribe intent verification request for the '{ReceiverName}' WebHook receiver -- verification passed, returning challenge response.", WebSubConstants.ReceiverName);
                return(new ContentResult {
                    Content = challengeValues
                });
            }
            else
            {
                _logger.LogInformation("Received an unsubscribe intent verification request for the '{ReceiverName}' WebHook receiver -- verification failed, returning challenge response.", WebSubConstants.ReceiverName);
                return(new NotFoundResult());
            }
        }
Пример #5
0
        public Task RemoveAsync(WebSubSubscription subscription, CancellationToken cancellationToken)
        {
            if (subscription != null)
            {
                _store.TryRemove(subscription.Id, out _);
            }

            return(Task.CompletedTask);
        }
        private WebSubRequestServices PrepareWebSubRequestServices(WebSubSubscription subscription = null)
        {
            subscription = subscription ?? PrepareWebSubSubscription();

            return(new WebSubRequestServices(new Dictionary <string, WebSubSubscription>
            {
                { subscription.Id, subscription }
            }));
        }
Пример #7
0
        private async Task <HttpResponseMessage> VerifyContentDistribution(WebSubSubscription subscription, HttpRequestMessage request)
        {
            if (subscription.State != WebSubSubscriptionState.SubscribeValidated)
            {
                return(request.CreateResponse(HttpStatusCode.NotFound));
            }

            if (String.IsNullOrWhiteSpace(subscription.Secret))
            {
                return(null);
            }

            string signatureHeader;

            try
            {
                signatureHeader = GetRequestHeader(request, SIGNATURE_HEADER_NAME);
            }
            catch (HttpResponseException)
            {
                return(HandleInvalidSignatureHeader(request));
            }

            string[] tokens = signatureHeader.SplitAndTrim('=');
            if (tokens.Length != 2)
            {
                return(HandleInvalidSignatureHeader(request));
            }

            byte[] signatureHeaderExpectedHash;
            try
            {
                signatureHeaderExpectedHash = EncodingUtilities.FromHex(tokens[1]);
            }
            catch (Exception)
            {
                return(HandleBadRequest(request, $"The '{SIGNATURE_HEADER_NAME}' header value is invalid. The '{RECEIVER_NAME}' WebHook receiver requires a valid hex-encoded string."));
            }

            byte[] payloadActualHash = await ComputeRequestBodyHashAsync(request, tokens[0], Encoding.UTF8.GetBytes(subscription.Secret));

            if (payloadActualHash == null)
            {
                return(HandleInvalidSignatureHeader(request));
            }

            if (!SecretEqual(signatureHeaderExpectedHash, payloadActualHash))
            {
                return(HandleBadRequest(request, $"The signature provided by the '{SIGNATURE_HEADER_NAME}' header field does not match the value expected by the '{RECEIVER_NAME}' WebHook receiver. WebHook request is invalid."));
            }

            return(null);
        }
Пример #8
0
        private async Task <IActionResult> VerifyContentDistributionRequest(WebSubSubscription subscription, HttpRequest request)
        {
            if (subscription.State != WebSubSubscriptionState.SubscribeValidated)
            {
                return(new NotFoundResult());
            }

            if (String.IsNullOrWhiteSpace(subscription.Secret))
            {
                return(null);
            }

            string signatureHeader = GetRequestHeader(request, WebSubConstants.SIGNATURE_HEADER_NAME, out IActionResult verificationResult);

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

            TrimmingTokenizer tokens = new TrimmingTokenizer(signatureHeader, _pairSeparators);

            if (tokens.Count != 2)
            {
                return(HandleInvalidSignatureHeader());
            }

            TrimmingTokenizer.Enumerator tokensEnumerator = tokens.GetEnumerator();

            tokensEnumerator.MoveNext();
            StringSegment signatureHeaderKey = tokensEnumerator.Current;

            tokensEnumerator.MoveNext();
            byte[] signatureHeaderExpectedHash = FromHex(tokensEnumerator.Current.Value, WebSubConstants.SIGNATURE_HEADER_NAME);
            if (signatureHeaderExpectedHash == null)
            {
                return(CreateBadHexEncodingResult(WebSubConstants.SIGNATURE_HEADER_NAME));
            }

            byte[] payloadActualHash = await ComputeRequestBodyHashAsync(request, signatureHeaderKey, Encoding.UTF8.GetBytes(subscription.Secret));

            if (payloadActualHash == null)
            {
                return(HandleInvalidSignatureHeader());
            }

            if (!SecretEqual(signatureHeaderExpectedHash, payloadActualHash))
            {
                return(CreateBadSignatureResult(WebSubConstants.SIGNATURE_HEADER_NAME));
            }

            return(null);
        }
        /// <summary>
        /// Creates new, initialized and already stored <see cref="WebSubSubscription"/>.
        /// </summary>
        /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public async Task <WebSubSubscription> CreateAsync(CancellationToken cancellationToken)
        {
            WebSubSubscription subscription = new WebSubSubscription
            {
                Id    = Guid.NewGuid().ToString("D"),
                State = WebSubSubscriptionState.Created
            };

            _webSubDbContext.Subscriptions.Add(subscription);

            await _webSubDbContext.SaveChangesAsync(cancellationToken);

            return(subscription);
        }
Пример #10
0
        public Task <WebSubSubscription> CreateAsync(CancellationToken cancellationToken)
        {
            WebSubSubscription subscription = new WebSubSubscription
            {
                Id    = Guid.NewGuid().ToString("D"),
                State = WebSubSubscriptionState.Created
            };

            if (!_store.TryAdd(subscription.Id, subscription))
            {
                throw new Exception("Error while creating subscription");
            }

            return(Task.FromResult(subscription));
        }
Пример #11
0
        /// <summary>
        /// Processes the incoming WebHook request. The request may be an initialization request or it may be
        /// an actual WebHook request. It is up to the receiver to determine what kind of incoming request it
        /// is and process it accordingly.
        /// </summary>
        /// <param name="id">A (potentially empty) ID of a particular configuration for this <see cref="IWebHookReceiver"/>. This
        /// allows an <see cref="IWebHookReceiver"/> to support multiple WebHooks with individual configurations.</param>
        /// <param name="context">The <see cref="HttpRequestContext"/> for the incoming request.</param>
        /// <param name="request">The <see cref="HttpRequestMessage"/> containing the incoming WebHook.</param>
        public override async Task <HttpResponseMessage> ReceiveAsync(string id, HttpRequestContext context, HttpRequestMessage request)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }

            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            WebSubSubscription subscription = await request.GetWebSubSubscriptionsStore().RetrieveAsync(id);

            if (subscription != null)
            {
                if (request.Method == HttpMethod.Get)
                {
                    return(await HandleIntentVerificationAsync(subscription, request));
                }
                else if (request.Method == HttpMethod.Post)
                {
                    HttpResponseMessage contentDistributionVerificationResponse = await VerifyContentDistribution(subscription, request);

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

                    return(await ExecuteWebHookAsync(id, context, request, Enumerable.Empty <string>(), new WebSubContent(request)));
                }
                else
                {
                    return(CreateBadMethodResponse(request));
                }
            }
            else
            {
                return(request.CreateResponse(HttpStatusCode.NotFound));
            }
        }
        private ResourceExecutingContext PrepareIntentVerificatioResourceExecutingContext(
            string mode                      = INTENT_VERIFICATION_MODE_SUBSCRIBE,
            string topic                     = WEBSUB_ROCKS_TOPIC_URL,
            string challenge                 = WEBSUB_ROCKS_CHALLENGE,
            string leaseSeconds              = WEBSUB_ROCKS_LEASE_SECONDS,
            string reason                    = null,
            WebSubSubscription subscription  = null,
            IServiceProvider requestServices = null)
        {
            subscription = subscription ?? PrepareWebSubSubscription();

            return(new ResourceExecutingContext(new ActionContext()
            {
                ActionDescriptor = new ActionDescriptor(),
                HttpContext = new IntentVerificationHttpContext(mode, topic, challenge, leaseSeconds, reason, subscription, requestServices ?? PrepareWebSubRequestServices(subscription)),
                RouteData = new RouteData()
            }, new List <IFilterMetadata>(), new List <IValueProviderFactory>()));
        }
        public async Task <SubscriptionViewModel> Subscribe(SubscribeViewModel subscribeViewModel, WebSubSubscriber webSubSubscriber)
        {
            if (!ModelState.IsValid)
            {
                return(new SubscriptionViewModel(ModelState));
            }

            WebSubSubscription webSubSubscription = null;

            try
            {
                webSubSubscription = await _webSubSubscriptionsStore.CreateAsync();

                webSubSubscription.CallbackUrl = Request.GetWebSubWebHookUrl(webSubSubscription.Id);

                WebSubSubscribeParameters webSubSubscribeParameters = new WebSubSubscribeParameters(subscribeViewModel.Url, webSubSubscription.CallbackUrl)
                {
                    OnDiscoveredAsync = async(WebSubDiscoveredUrls discovery, CancellationToken cancellationToken) =>
                    {
                        webSubSubscription.State    = WebSubSubscriptionState.SubscribeRequested;
                        webSubSubscription.TopicUrl = discovery.Topic;

                        await _webSubSubscriptionsStore.UpdateAsync(webSubSubscription);
                    }
                };

                if (!String.IsNullOrWhiteSpace(subscribeViewModel.Secret))
                {
                    webSubSubscription.Secret        = subscribeViewModel.Secret;
                    webSubSubscribeParameters.Secret = subscribeViewModel.Secret;
                }


                webSubSubscription.HubUrl = (await webSubSubscriber.SubscribeAsync(webSubSubscribeParameters, HttpContext.RequestAborted)).Hub;

                return(new SubscriptionViewModel(webSubSubscription));
            }
            catch (Exception ex) when((ex is WebSubDiscoveryException) || (ex is WebSubSubscriptionException))
            {
                await _webSubSubscriptionsStore.RemoveAsync(webSubSubscription);

                return(new SubscriptionViewModel(ex));
            }
        }
Пример #14
0
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            IActionResult secureConnectionCheckResult = EnsureSecureConnection(ReceiverName, context.HttpContext.Request);

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

            if (!context.RouteData.TryGetWebHookReceiverId(out string subscriptionId))
            {
                context.Result = new NotFoundResult();
                return;
            }

            WebSubSubscription subscription = await RetrieveWebSubSubscriptionAsync(context, subscriptionId);

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

            if (HttpMethods.IsPost(context.HttpContext.Request.Method))
            {
                IActionResult contentDistributionRequestVerificationResult = await VerifyContentDistributionRequest(subscription, context.HttpContext.Request);

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

            context.HttpContext.Items[WebSubConstants.HTTP_CONTEXT_ITEMS_SUBSCRIPTION_KEY] = subscription;

            await next();
        }
Пример #15
0
        /// <summary>
        /// Called asynchronously before the rest of the pipeline.
        /// </summary>
        /// <param name="context">The <see cref="ResourceExecutingContext"/>.</param>
        /// <param name="next">The <see cref="ResourceExecutionDelegate"/>. Invoked to execute the next resource filter or the remainder of the pipeline.</param>
        /// <returns>A <see cref="Task"/> which will complete when the remainder of the pipeline completes.</returns>
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            IQueryCollection requestQuery = context.HttpContext.Request.Query;

            if (HttpMethods.IsGet(context.HttpContext.Request.Method) && requestQuery.ContainsKey(WebSubConstants.MODE_QUERY_PARAMETER_NAME))
            {
                WebSubSubscription subscription = context.HttpContext.Items[WebSubConstants.HTTP_CONTEXT_ITEMS_SUBSCRIPTION_KEY] as WebSubSubscription;
                context.Result = await HandleIntentVerificationAsync(subscription, requestQuery, context.HttpContext.RequestServices);
            }
            else
            {
                await next();
            }
        }
Пример #16
0
        private async Task <IActionResult> HandleSubscribeIntentDenyAsync(WebSubSubscription subscription, IWebSubSubscriptionsStore subscriptionsStore, IWebSubSubscriptionsService subscriptionsService, IQueryCollection requestQuery)
        {
            StringValues topicValues = requestQuery[WebSubConstants.TOPIC_QUERY_PARAMETER_NAME];

            if (StringValues.IsNullOrEmpty(topicValues))
            {
                return(HandleBadRequest($"A '{WebSubConstants.ReceiverName}' WebHook subscribe intent deny request must contain a '{WebSubConstants.TOPIC_QUERY_PARAMETER_NAME}' query parameter."));
            }
            StringValues reason = requestQuery[WebSubConstants.INTENT_DENY_REASON_QUERY_PARAMETER_NAME];

            subscription.State = WebSubSubscriptionState.SubscribeDenied;
            await subscriptionsStore.UpdateAsync(subscription);

            if (subscriptionsService != null)
            {
                await subscriptionsService.OnSubscribeIntentDenyAsync(subscription, reason, subscriptionsStore);
            }

            _logger.LogInformation("Received a subscribe intent deny request for the '{ReceiverName}' WebHook receiver -- subscription denied, returning confirmation response.", WebSubConstants.ReceiverName);

            return(new NoContentResult());
        }
Пример #17
0
        public async Task <IActionResult> HandlerForContentDistribution(string id, WebSubSubscription subscription, IWebSubContent content)
        {
            await _serverSentEventsService.SendEventAsync($"HandlerForContentDistribution ({id})");

            return(Ok());
        }
Пример #18
0
 public Task UpdateAsync(WebSubSubscription subscription)
 {
     return(UpdateAsync(subscription, CancellationToken.None));
 }
Пример #19
0
 public Task UpdateAsync(WebSubSubscription webSubSubscription, CancellationToken cancellationToken)
 {
     return(Task.CompletedTask);
 }
Пример #20
0
 public Task RemoveAsync(WebSubSubscription subscription)
 {
     return(RemoveAsync(subscription, CancellationToken.None));
 }
 /// <summary>
 /// Performs store update of previously retrieved and updated <see cref="WebSubSubscription"/>.
 /// </summary>
 /// <param name="subscription">The <see cref="WebSubSubscription"/> to be updated in store.</param>
 /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
 /// <returns>The task object representing the asynchronous operation.</returns>
 public Task UpdateAsync(WebSubSubscription subscription, CancellationToken cancellationToken)
 {
     return(_webSubDbContext.SaveChangesAsync(cancellationToken));
 }
        /// <summary>
        /// Removes from store <see cref="WebSubSubscription"/> based on its unique identifier.
        /// </summary>
        /// <param name="id">The unique identifier of the subscription.</param>
        /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public async Task RemoveAsync(string id, CancellationToken cancellationToken)
        {
            WebSubSubscription subscription = await RetrieveAsync(id, cancellationToken);

            await RemoveAsync(subscription, cancellationToken);
        }
Пример #23
0
 public SubscriptionViewModel(WebSubSubscription subscription)
 {
     Subscribed   = true;
     Subscription = subscription;
 }
Пример #24
0
 public Task UpdateAsync(WebSubSubscription subscription, CancellationToken cancellationToken)
 {
     return(Task.FromResult <Object>(null));
 }
Пример #25
0
            public Task <WebSubSubscription> RetrieveAsync(string id, CancellationToken cancellationToken)
            {
                WebSubSubscription subscription = _store.ContainsKey(id) ? _store[id] : null;

                return(Task.FromResult(subscription));
            }
        /// <summary>
        /// Removes from store previously retrieved <see cref="WebSubSubscription"/>.
        /// </summary>
        /// <param name="subscription">The <see cref="WebSubSubscription"/> to be removed from store.</param>
        /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public Task RemoveAsync(WebSubSubscription subscription, CancellationToken cancellationToken)
        {
            _webSubDbContext.Subscriptions.Remove(subscription);

            return(_webSubDbContext.SaveChangesAsync(cancellationToken));
        }
Пример #27
0
        private static async Task <bool> VerifySubscribeIntentAsync(HttpRequestMessage request, WebSubSubscription subscription, string topic, int leaseSeconds)
        {
            bool verified = false;

            if (subscription.State == WebSubSubscriptionState.SubscribeRequested)
            {
                IWebSubSubscriptionsStore   subscriptionsStore   = request.GetWebSubSubscriptionsStore();
                IWebSubSubscriptionsService subscriptionsService = request.GetWebSubSubscriptionsService();

                if (subscription.TopicUrl != topic)
                {
                    if (subscriptionsService != null)
                    {
                        await subscriptionsService.OnInvalidSubscribeIntentVerificationAsync(subscription, subscriptionsStore);
                    }
                }
                else if ((subscriptionsService == null) || (await subscriptionsService.OnSubscribeIntentVerificationAsync(subscription, subscriptionsStore)))
                {
                    subscription.State = WebSubSubscriptionState.SubscribeValidated;
                    subscription.VerificationRequestTimeStampUtc = DateTime.UtcNow;
                    subscription.LeaseSeconds = leaseSeconds;
                    await subscriptionsStore.UpdateAsync(subscription);

                    verified = true;
                }
            }

            return(verified);
        }
Пример #28
0
 public Task RemoveAsync(WebSubSubscription subscription, CancellationToken cancellationToken)
 {
     throw new NotImplementedException();
 }
Пример #29
0
 public Task RemoveAsync(WebSubSubscription subscription)
 {
     throw new NotImplementedException();
 }
Пример #30
0
        private static async Task <HttpResponseMessage> HandleUnsubscribeIntentVerificationAsync(HttpRequestMessage request, Dictionary <string, string> requestQuery, WebSubSubscription subscription)
        {
            if (!requestQuery.TryGetValue(TOPIC_QUERY_PARAMETER_NAME, out string topic) || String.IsNullOrEmpty(topic))
            {
                return(HandleMissingIntentVerificationParameter(request, TOPIC_QUERY_PARAMETER_NAME));
            }

            if (!requestQuery.TryGetValue(INTENT_VERIFICATION_CHALLENGE_QUERY_PARAMETER_NAME, out string challenge) || String.IsNullOrEmpty(challenge))
            {
                return(HandleMissingIntentVerificationParameter(request, INTENT_VERIFICATION_CHALLENGE_QUERY_PARAMETER_NAME));
            }

            if (await VerifyUnsubscribeIntentAsync(request, subscription, topic))
            {
                request.GetWebHooksLogger().Info($"Received an unsubscribe intent verification request for the '{ReceiverName}' WebHook receiver -- verification passed, returning challenge response.");
                return(CreateChallengeResponse(challenge));
            }
            else
            {
                request.GetWebHooksLogger().Info($"Received an unsubscribe intent verification request for the '{ReceiverName}' WebHook receiver -- verification failed, returning challenge response.");
                return(request.CreateResponse(HttpStatusCode.NotFound));
            }
        }