Improve zvonok verification call (#3768)
# What this PR does Added support for message template during phone number verification for the Zvonok service. Template message support allows the use of [SSML](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/) markup depending on the speaker used for speech synthesis. Example: `Your verification code is <prosody rate="x-slow">$verification_code</prosody>` ## Which issue(s) this PR fixes ## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Innokentii Konstantinov <innokenty.konstantinov@grafana.com> Co-authored-by: Joey Orlando <joey.orlando@grafana.com> Co-authored-by: Joey Orlando <joseph.t.orlando@gmail.com>
This commit is contained in:
parent
cb48842e49
commit
7cd814507e
7 changed files with 83 additions and 3 deletions
|
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Improved zvonok verification call @sreway ([#3768](https://github.com/grafana/oncall/pull/3768))
|
||||
|
||||
### Changed
|
||||
|
||||
- Allow mobile app to access escalation options endpoints @imtoori ([#3847](https://github.com/grafana/oncall/pull/3847))
|
||||
|
|
|
|||
|
|
@ -224,7 +224,9 @@ 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 process the call status, it is required to add a postback with the GET/POST method on the side of the zvonok.com
|
||||
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.`.
|
||||
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}`
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ class LiveSetting(models.Model):
|
|||
"ZVONOK_POSTBACK_STATUS",
|
||||
"ZVONOK_POSTBACK_USER_CHOICE",
|
||||
"ZVONOK_POSTBACK_USER_CHOICE_ACK",
|
||||
"ZVONOK_VERIFICATION_TEMPLATE",
|
||||
)
|
||||
|
||||
DESCRIPTIONS = {
|
||||
|
|
@ -167,6 +168,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).",
|
||||
}
|
||||
|
||||
SECRET_SETTING_NAMES = (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
from random import randint
|
||||
from string import Template
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
|
@ -104,15 +105,25 @@ class ZvonokPhoneProvider(PhoneProvider):
|
|||
return f"Failed make call to {number}"
|
||||
|
||||
def make_verification_call(self, number: str):
|
||||
code = str(randint(100000, 999999))
|
||||
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
|
||||
)
|
||||
else:
|
||||
message = f"Your verification code is {codewspaces}"
|
||||
try:
|
||||
response = self._call_create(number, f"Your verification code is {codewspaces}", speaker)
|
||||
response = self._call_create(
|
||||
number,
|
||||
message,
|
||||
speaker,
|
||||
)
|
||||
response.raise_for_status()
|
||||
body = response.json()
|
||||
if not body:
|
||||
|
|
@ -139,6 +150,9 @@ class ZvonokPhoneProvider(PhoneProvider):
|
|||
def _cache_key(self, number):
|
||||
return f"zvonok_provider_{number}"
|
||||
|
||||
def _generate_verification_code(self):
|
||||
return str(randint(100000, 999999))
|
||||
|
||||
@property
|
||||
def flags(self) -> ProviderFlags:
|
||||
return ProviderFlags(
|
||||
|
|
|
|||
0
engine/apps/zvonok/tests/__init__.py
Normal file
0
engine/apps/zvonok/tests/__init__.py
Normal file
57
engine/apps/zvonok/tests/test_zvonok_provider.py
Normal file
57
engine/apps/zvonok/tests/test_zvonok_provider.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from django.test import override_settings
|
||||
|
||||
from apps.zvonok.phone_provider import ZvonokPhoneProvider
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def provider():
|
||||
return ZvonokPhoneProvider()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_make_verification_call_with_template_set(provider):
|
||||
verification_code = "123456"
|
||||
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):
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_make_verification_call_with_invalid_template_set(provider):
|
||||
verification_code = "123456"
|
||||
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)
|
||||
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"
|
||||
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):
|
||||
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)
|
||||
|
|
@ -842,6 +842,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)
|
||||
|
||||
DETACHED_INTEGRATIONS_SERVER = getenv_boolean("DETACHED_INTEGRATIONS_SERVER", default=False)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue