change zvonok call verification (#4393)
# Change zvonok call verification After May 27, the Zvonok service will block number verification that was not set up as part of the phone number verification campaign. This PR modifies the number verification process. <!-- *Note*: if you have more than one GitHub issue that this PR closes, be sure to preface each issue link with a [closing keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue). This ensures that the issue(s) are auto-closed once the PR has been merged. --> ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. Co-authored-by: Innokentii Konstantinov <innokenty.konstantinov@grafana.com>
This commit is contained in:
parent
8539514d85
commit
d0dd15453e
5 changed files with 42 additions and 57 deletions
|
|
@ -234,8 +234,7 @@ Zvonok.com, complete the following steps:
|
|||
to the variable `ZVONOK_AUDIO_ID` (optional step).
|
||||
6. To make a call with a specific voice, you can set the `ZVONOK_SPEAKER_ID`.
|
||||
By default, the ID used is `Salli` (optional step).
|
||||
7. To change the voice message for phone verification, you can set the variable `ZVONOK_VERIFICATION_TEMPLATE`
|
||||
with the following format (optional step): `Your verification code is $verification_code, have a nice day.`.
|
||||
7. Create phone number verification campaign with type `tellcode` and assign its ID value to `ZVONOK_VERIFICATION_CAMPAIGN_ID`.
|
||||
8. To process the call status, it is required to add a postback with the GET/POST method on the side of the zvonok.com
|
||||
service with the following format (optional step):
|
||||
`${ONCALL_BASE_URL}/zvonok/call_status_events?campaign_id={ct_campaign_id}&call_id={ct_call_id}&status={ct_status}&user_choice={ct_user_choice}`
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class LiveSetting(models.Model):
|
|||
"ZVONOK_POSTBACK_STATUS",
|
||||
"ZVONOK_POSTBACK_USER_CHOICE",
|
||||
"ZVONOK_POSTBACK_USER_CHOICE_ACK",
|
||||
"ZVONOK_VERIFICATION_TEMPLATE",
|
||||
"ZVONOK_VERIFICATION_CAMPAIGN_ID",
|
||||
)
|
||||
|
||||
DESCRIPTIONS = {
|
||||
|
|
@ -170,7 +170,7 @@ class LiveSetting(models.Model):
|
|||
"ZVONOK_POSTBACK_STATUS": "'Postback' status (ct_status) query parameter name to validate a postback request.",
|
||||
"ZVONOK_POSTBACK_USER_CHOICE": "'Postback' user choice (ct_user_choice) query parameter name (optional).",
|
||||
"ZVONOK_POSTBACK_USER_CHOICE_ACK": "'Postback' user choice (ct_user_choice) query parameter value for acknowledge alert group (optional).",
|
||||
"ZVONOK_VERIFICATION_TEMPLATE": "The message template used for phone number verification (optional).",
|
||||
"ZVONOK_VERIFICATION_CAMPAIGN_ID": "The phone number verification campaign ID. You can get it after verification campaign creation.",
|
||||
}
|
||||
|
||||
SECRET_SETTING_NAMES = (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import logging
|
||||
from random import randint
|
||||
from string import Template
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
|
@ -12,6 +11,7 @@ from apps.phone_notifications.phone_provider import PhoneProvider, ProviderFlags
|
|||
from apps.zvonok.models.phone_call import ZvonokCallStatuses, ZvonokPhoneCall
|
||||
|
||||
ZVONOK_CALL_URL = "https://zvonok.com/manager/cabapi_external/api/v1/phones/call/"
|
||||
ZVONOK_VERIFICATION_CALL_URL = "https://zvonok.com/manager/cabapi_external/api/v1/phones/tellcode/"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -96,6 +96,15 @@ class ZvonokPhoneProvider(PhoneProvider):
|
|||
|
||||
return requests.post(ZVONOK_CALL_URL, params=params)
|
||||
|
||||
def _verification_call_create(self, number: str, code: int):
|
||||
params = {
|
||||
"public_key": live_settings.ZVONOK_API_KEY,
|
||||
"campaign_id": live_settings.ZVONOK_VERIFICATION_CAMPAIGN_ID,
|
||||
"phone": number,
|
||||
"pincode": code,
|
||||
}
|
||||
return requests.post(ZVONOK_VERIFICATION_CALL_URL, params=params)
|
||||
|
||||
def _get_graceful_msg(self, body, number):
|
||||
if body:
|
||||
status = body.get("status")
|
||||
|
|
@ -105,34 +114,19 @@ class ZvonokPhoneProvider(PhoneProvider):
|
|||
return f"Failed make call to {number}"
|
||||
|
||||
def make_verification_call(self, number: str):
|
||||
body = None
|
||||
code = self._generate_verification_code()
|
||||
cache.set(self._cache_key(number), code, timeout=10 * 60)
|
||||
codewspaces = " ".join(code)
|
||||
|
||||
body = None
|
||||
speaker = live_settings.ZVONOK_SPEAKER_ID
|
||||
|
||||
if live_settings.ZVONOK_VERIFICATION_TEMPLATE:
|
||||
message = Template(live_settings.ZVONOK_VERIFICATION_TEMPLATE).safe_substitute(
|
||||
verification_code=codewspaces
|
||||
if not live_settings.ZVONOK_VERIFICATION_CAMPAIGN_ID:
|
||||
raise FailedToStartVerification(
|
||||
graceful_msg="Failed make verification call, verification campaign id not set."
|
||||
)
|
||||
else:
|
||||
message = f"Your verification code is {codewspaces}"
|
||||
|
||||
try:
|
||||
response = self._call_create(
|
||||
number,
|
||||
message,
|
||||
speaker,
|
||||
)
|
||||
response.raise_for_status()
|
||||
response = self._verification_call_create(number, code)
|
||||
body = response.json()
|
||||
if not body:
|
||||
logger.error("ZvonokPhoneProvider.make_verification_call: failed, empty body")
|
||||
raise FailedToMakeCall(graceful_msg=f"Failed make verification call to {number}, empty body")
|
||||
|
||||
call_id = body.get("call_id")
|
||||
if not call_id:
|
||||
raise FailedToStartVerification(graceful_msg=self._get_graceful_msg(body, number))
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.HTTPError as http_err:
|
||||
logger.error(f"ZvonokPhoneProvider.make_verification_call: failed {http_err}")
|
||||
raise FailedToStartVerification(graceful_msg=self._get_graceful_msg(body, number))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch
|
|||
import pytest
|
||||
from django.test import override_settings
|
||||
|
||||
from apps.phone_notifications.exceptions import FailedToStartVerification
|
||||
from apps.zvonok.phone_provider import ZvonokPhoneProvider
|
||||
|
||||
|
||||
|
|
@ -12,46 +13,37 @@ def provider():
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_make_verification_call_with_template_set(provider):
|
||||
verification_code = "123456"
|
||||
def test_make_verification_call(provider):
|
||||
verification_code = "123456789"
|
||||
number = "1234567890"
|
||||
speaker_id = "Salli"
|
||||
template_value = 'Your code is <prosody rate="x-slow">$verification_code</prosody>'
|
||||
excepted_message = 'Your code is <prosody rate="x-slow">1 2 3 4 5 6</prosody>'
|
||||
|
||||
with override_settings(ZVONOK_VERIFICATION_TEMPLATE=template_value, ZVONOK_SPEAKER_ID=speaker_id):
|
||||
campaign_id = "123456"
|
||||
with override_settings(ZVONOK_VERIFICATION_CAMPAIGN_ID=campaign_id):
|
||||
with patch("django.core.cache.cache.set"):
|
||||
provider._call_create = MagicMock(return_value=MagicMock(json=lambda: {"call_id": "12345"}))
|
||||
provider._verification_call_create = MagicMock(return_value=MagicMock(json=lambda: {"status": "ok"}))
|
||||
provider._generate_verification_code = MagicMock(return_value=verification_code)
|
||||
provider.make_verification_call(number)
|
||||
provider._call_create.assert_called_once_with(number, excepted_message, speaker_id)
|
||||
provider._verification_call_create.assert_called_once_with(number, verification_code)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_make_verification_call_with_invalid_template_set(provider):
|
||||
verification_code = "123456"
|
||||
def test_make_verification_call_without_campaign_id(provider):
|
||||
number = "1234567890"
|
||||
speaker_id = "Salli"
|
||||
template_value = "Your code is"
|
||||
excepted_message = "Your code is"
|
||||
|
||||
with override_settings(ZVONOK_VERIFICATION_TEMPLATE=template_value, ZVONOK_SPEAKER_ID=speaker_id):
|
||||
with patch("django.core.cache.cache.set"):
|
||||
provider._call_create = MagicMock(return_value=MagicMock(json=lambda: {"call_id": "12345"}))
|
||||
provider._generate_verification_code = MagicMock(return_value=verification_code)
|
||||
with patch("django.core.cache.cache.set"):
|
||||
with pytest.raises(FailedToStartVerification):
|
||||
provider.make_verification_call(number)
|
||||
provider._call_create.assert_called_once_with(number, excepted_message, speaker_id)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_make_verification_call_without_template_set(provider):
|
||||
verification_code = "123456"
|
||||
def test_make_verification_call_with_error(provider):
|
||||
number = "1234567890"
|
||||
speaker_id = "Salli"
|
||||
excepted_message = "Your verification code is 1 2 3 4 5 6"
|
||||
with override_settings(ZVONOK_SPEAKER_ID=speaker_id):
|
||||
campaign_id = "123456"
|
||||
|
||||
with override_settings(ZVONOK_VERIFICATION_CAMPAIGN_ID=campaign_id):
|
||||
with patch("django.core.cache.cache.set"):
|
||||
provider._call_create = MagicMock(return_value=MagicMock(json=lambda: {"call_id": "12345"}))
|
||||
provider._generate_verification_code = MagicMock(return_value=verification_code)
|
||||
provider.make_verification_call(number)
|
||||
provider._call_create.assert_called_once_with(number, excepted_message, speaker_id)
|
||||
with pytest.raises(FailedToStartVerification):
|
||||
provider._verification_call_create = MagicMock(
|
||||
return_value=MagicMock(
|
||||
json={"status": "error", "data": "Form isn't valid: * campaign_id\n * Invalid campaign type"}
|
||||
)
|
||||
)
|
||||
provider.make_verification_call(number)
|
||||
|
|
|
|||
|
|
@ -912,7 +912,7 @@ ZVONOK_POSTBACK_CAMPAIGN_ID = os.getenv("ZVONOK_POSTBACK_CAMPAIGN_ID", "campaign
|
|||
ZVONOK_POSTBACK_STATUS = os.getenv("ZVONOK_POSTBACK_STATUS", "status")
|
||||
ZVONOK_POSTBACK_USER_CHOICE = os.getenv("ZVONOK_POSTBACK_USER_CHOICE", None)
|
||||
ZVONOK_POSTBACK_USER_CHOICE_ACK = os.getenv("ZVONOK_POSTBACK_USER_CHOICE_ACK", None)
|
||||
ZVONOK_VERIFICATION_TEMPLATE = os.getenv("ZVONOK_VERIFICATION_TEMPLATE", None)
|
||||
ZVONOK_VERIFICATION_CAMPAIGN_ID = os.getenv("ZVONOK_VERIFICATION_CAMPAIGN_ID", None)
|
||||
|
||||
DETACHED_INTEGRATIONS_SERVER = getenv_boolean("DETACHED_INTEGRATIONS_SERVER", default=False)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue