wadebug/wa_actions/implementations/check_webhook.py (129 lines of code) (raw):
# Copyright (c) Facebook, Inc. and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import absolute_import, division, print_function, unicode_literals
import os
import tempfile
from wadebug import results
from wadebug.exceptions import (
WABizAccessError,
WABizAuthError,
WABizGeneralError,
WABizNetworkError,
)
from wadebug.wa_actions import curl_utils, docker_utils
from wadebug.wa_actions.base import WAAction
from wadebug.wa_actions.curl_utils import CURLTestResult
from wadebug.wa_actions.wabiz_api import WABizAPI
REQ_TIMEOUT = 5
ACCEPTABLE_RESPONSE_TIME = 3
TEST_POST_DATA_STRING = '{"wadebug_webhook_test":"test"}'
DEST_PATH = "/usr/local/waent"
class CheckWebhookAction(WAAction):
user_facing_name = "check_webhook"
short_description = "Test if the webhook is accessible and responsive"
config_dependencies = ("webapp.baseUrl", "webapp.user", "webapp.password")
@classmethod
def _run(cls, config, *args, **kwargs):
api = WABizAPI(**config.get("webapp"))
try:
webhook_url = api.get_webhook_url()
if not webhook_url:
return _result_webhook_not_set(cls)
if not webhook_url.startswith("https"):
return _result_webhook_not_https(cls)
except (WABizAccessError, WABizAuthError, WABizNetworkError, ValueError) as e:
return _result_get_webhook_error(cls, e)
# explicitly catching a possible exception
except WABizGeneralError as e:
# WABizGeneralError is likely not a user error,
# should be handled by app-wide catch
raise e
wacore_containers = docker_utils.get_running_wacore_containers()
if not wacore_containers:
return results.Problem(
cls,
"Webhook check failed",
"There is no wacore container running",
"Please check results from other actions to diagnose",
)
container = wacore_containers[0]
cert_str = api.get_webhook_cert()
dest_cert = None
if cert_str:
with tempfile.NamedTemporaryFile() as cert_file:
cert_file.write(cert_str)
cert_file.seek(0)
docker_utils.put_archive_to_container(
container, cert_file.name, DEST_PATH
)
dest_cert = os.path.join(DEST_PATH, os.path.basename(cert_file.name))
result, response_time = curl_utils.https_post_request_from_container(
container, webhook_url, TEST_POST_DATA_STRING, REQ_TIMEOUT, dest_cert
)
if result == CURLTestResult.CONNECTION_ERROR:
return _result_webhook_could_not_connect(cls)
elif result == CURLTestResult.SSL_CERT_UNKNOWN:
return _result_webhook_no_cert_uploaded(cls)
elif result == CURLTestResult.CONNECTION_TIMEOUT:
return _result_webhook_did_not_respond(cls)
elif result == CURLTestResult.HTTP_STATUS_NOT_OK:
return _result_webhook_did_not_return_ok(cls)
elif response_time > ACCEPTABLE_RESPONSE_TIME:
return _result_webhook_slow_response(cls, response_time)
return results.OK(cls)
def _result_webhook_not_set(cls):
return results.Warning(
cls,
"Unable to check webhook",
"Webhook has not been set",
"Set up webhook via App Settings "
"https://developers.facebook.com/docs/whatsapp/api/settings/app",
"",
)
def _result_webhook_not_https(cls):
return results.Problem(
cls,
"Webhook not secure",
"Webhook is not using https",
"Please set a webhook endpoint that is https enabled",
"",
)
def _result_webhook_no_cert_uploaded(cls):
return results.Problem(
cls,
"SSL verification error",
"Unable to connect securely to the webhook",
"Please upload self-signed cert (and restart coreapp) "
"or use a publicly known cert",
"",
)
def _result_get_webhook_error(cls, exception):
results.Problem(cls, "Unable to check webhook", str(exception), "")
def _result_webhook_could_not_connect(cls):
return results.Problem(
cls,
"Webhook unresponsive",
"Could not establish a connection to the webhook".format(REQ_TIMEOUT),
"Please check to make sure your webhook endpoint is up, or "
"have configured the webhook setting correctly",
)
def _result_webhook_did_not_respond(cls):
return results.Problem(
cls,
"Webhook unresponsive",
"Webhook did not respond within {} seconds".format(REQ_TIMEOUT),
"Please ensure you are returning 200 OK "
"from your webhook handler as soon as possible."
"If processing is needed, we recommend returning 200 OK "
"and doing processing later.",
)
def _result_webhook_did_not_return_ok(cls):
return results.Problem(
cls,
"Unexpected webhook response",
"Webhook did not return 200 OK",
"Please ensure your webhook handler returns 200 OK",
)
def _result_webhook_slow_response(cls, response_time):
return results.Warning(
cls,
"Slow webhook response",
"Webhook responded in {} seconds".format(response_time),
"Please ensure you are returning 200 OK "
"from your webhook handler as soon as possible",
)