Для многих API желательно аутентифицировать запросы, отправляемые на конечную точку. Для API системы интерактивного голосового ответа (IVR), возвращающей TwiML, единственным объектом, которому, вероятно, следует разрешить доступ в продакшене, является Twilio. В этой статье рассматривается реализация проверки подписи запроса в веб-приложении IVR на Python с использованием фреймворка Pyramid.
Из множества способов создать публичный URL для предоставления TwiML Twilio одним из самых простых является ngrok. В этом руководстве будет использоваться ngrok, который можно скачать здесь. После загрузки и распаковки ngrok следует выполнить следующую команду из директории, где он находится:
Когда вышеуказанная команда будет выполнена в терминале, появится следующий вывод:
В этом примере будет использоваться HTTPS-ссылка. Скопируйте её, и мы вставим её в следующем разделе.
Для выполнения этого примера требуется аккаунт Twilio, так как токен аутентификации, связанный с аккаунтом, является неотъемлемой частью проверки запроса. Аккаунт Twilio можно создать здесь.
После входа в аккаунт Twilio перейдите в раздел телефонных номеров и выберите номер, который будет использоваться в этом примере. Затем настройте входящий вебхук на странице конфигурации следующим образом и убедитесь, что в конце стоит косая черта:
Обратите внимание, что использование переменной окружения `TWILIO_AUTH_TOKEN` предотвращает включение этой конфиденциальной информации в файл конфигурации или исходный код, где она может случайно попасть под контроль версий. В продакшен-системах может быть желательно использовать более надёжное решение для безопасного включения токена аутентификации в приложение Twilio.
Теперь приложение должно быть доступно для Twilio, и звонок на настроенный в предыдущем разделе номер телефона должен привести к следующему выводу в терминале, обслуживающем приложение Pyramid:
Когда вебхук, настроенный для обработки входящих звонков, получает запрос от Twilio, в заголовке запроса будет содержаться значение X-Twilio-Signature. Это значение вместе с токеном аутентификации аккаунта — всё, что необходимо для определения, действительно ли запрос исходит от Twilio.
Примечание:
*Процесс генерации этого значения заголовка описан здесь*.
При определении, следует ли разрешать запрос к определённой конечной точке, Pyramid учитывает два понятия: аутентификацию и авторизацию. Аутентификация может рассматриваться как проверка того, является ли запрашивающий тем, за кого себя выдаёт, а авторизация — как проверка того, имеет ли запрашивающий разрешение на выполнение запрашиваемого действия.
Проверка подписей запросов относится к аутентификации и должна обрабатываться через политику аутентификации приложения Pyramid. Pyramid не включает политики аутентификации, которые идеально подходили бы только для проверки подписей запросов Twilio. Все они сосредоточены вокруг идентификатора пользователя и способствуют созданию и использованию сессий. AuthTktCookieHelper от Pyramid близок к хорошему решению, но предоставляет много дополнительных функций, которые не нужны для простой, не имеющей состояния проверки запросов.
К счастью, Pyramid позволяет легко определить и включить пользовательскую политику аутентификации. Каноническая политика аутентификации должна реализовывать интерфейс IAuthenticationPolicy и определять все его методы; однако это приведёт к созданию гораздо более функциональной политики, чем необходимо для данного примера.
Примечание:
*Twilio также поддерживает базовую HTTP-аутентификацию, и Pyramid включает BasicAuthAuthenticationPolicy из коробки. Это руководство фокусируется только на аутентификации с помощью дайджеста, которая может быть предпочтительнее, так как не требует включения учётных данных в каждый URL запроса, где они могут быть подвержены перехвату.*
Пользовательская политика TwilioSignatureAuthenticationPolicy, определённая для этого примера, содержит определение метода `effective_principals`. Это единственный метод, который Pyramid требует для совместимости политики аутентификации с ACLAuthorizationPolicy. Приложение Pyramid вызывает метод `effective_principals` политики аутентификации для каждого HTTP-запроса к одной из его конечных точек, что делает это удобным местом для включения `RequestValidator` от Twilio.
Если подпись, сгенерированная `RequestValidator`, не совпадает с `X-Twilio-Signature`, прикреплённой к запросу, то политика аутентификации не включит основной объект Twilio в список эффективных принципалов, и запрашивающему будет отказано в доступе к любой конечной точке, требующей разрешения «view».
URL, передаваемый в метод RequestValidator.validate, должен быть идентичен тому, который используется Twilio. Необходимо учитывать такие моменты, как завершающие косые черты и схемы URI запроса — либо http, либо https в случае с Twilio.
По умолчанию метод Pyramid request.url будет генерировать URL, заканчивающийся косой чертой для корневого URL, тогда как ngrok отображает корневой URL без неё:
Если приложение Pyramid, настроенное для обработки звонков, находится за балансировщиком нагрузки или любым сервисом, который завершает TLS, критически важно, чтобы эти строки присутствовали в файле конфигурации приложения. Их отсутствие приведёт к тому, что вызовы различных методов генерации URL Pyramid будут возвращать URL с неверной схемой (например, http://ivr.example.com вместо https://ivr.example.com).
Ещё один момент, который следует помнить для GET-запросов: Twilio передаёт различные параметры строки запроса при обращении к вебхуку; однако для POST-запросов эта информация находится в теле запроса. Это может стать камнем преткновения для пользователей Pyramid, так как широко используемое свойство request.params объединяет параметры строки запроса с телом запроса для удобства. Убедитесь, что для целей проверки используется свойство request.POST. Это свойство вернёт пустой объект, подобный словарю, для GET-запросов, избегая помех при проверке. Ниже показано поведение `request.POST` во время HTTP GET:
В случае возникновения проблем с API, пытающимся предоставить TwiML, будь то из-за ошибки проверки запроса или по другой причине, Twilio воспроизведёт стандартное сообщение об ошибке для звонящего и завершит звонок. По умолчанию звонящий слышит:
«Извините, произошла ошибка приложения. До свидания».
Может быть желательно обрабатывать ошибки кастомизированным образом и, например, перенаправлять звонящих в колл-центр в случае исключения в API. Pyramid предоставляет набор декораторов представлений, чтобы сделать перехват и обработку ошибок довольно простыми. В этом примере будет возвращён кастомный TwiML, и звонящий будет перенаправлен в колл-центр для получения помощи:
Приведённый выше код определяет представления для обработки ошибок 403, 404 и 500. В случае возникновения одной из этих ошибок пользователю будет прочитано определённое выше кастомное сообщение. Важно, чтобы код статуса был установлен на 200, так как в противном случае Twilio проигнорирует отправленный TwiML и вернётся к стандартному сообщению об ошибке.
Для получения дополнительной информации о создании приложений на Python с использованием Twilio Voice API ознакомьтесь с документацией по быстрому старту.
Если при выполнении этого руководства возникнут какие-либо проблемы, пожалуйста, сообщите о них в трекере проблем GitHub репозитория, чтобы они могли быть решены своевременно.
Мои контактные данные доступны на https://patrick.yevsukov.com/, а мой профиль на GitHub — https://github.com/patrickyevsukov/.