Source code for otp_twilio.models

import logging

from django_otp.models import SideChannelDevice, ThrottlingMixin
from django_otp.util import hex_validator, random_hex
import requests

from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils.encoding import force_str

from .conf import settings

logger = logging.getLogger(__name__)


def default_key():  # pragma: no cover
    """Obsolete code here for migrations."""
    return force_str(random_hex(20))


def key_validator(value):  # pragma: no cover
    """Obsolete code here for migrations."""
    return hex_validator(20)(value)


[docs] class TwilioSMSDevice(ThrottlingMixin, SideChannelDevice): """ A :class:`~django_otp.models.SideChannelDevice` that delivers a token via the Twilio SMS service. The tokens are valid for :setting:`OTP_TWILIO_TOKEN_VALIDITY` seconds. Once a token has been accepted, it is no longer valid. .. attribute:: number *CharField*: The mobile phone number to deliver to. `Twilio recommends <https://www.twilio.com/docs/api/rest/sending-messages>`_ using the `E.164 <http://en.wikipedia.org/wiki/E.164>`_ format. For US numbers, this would look like '+15555555555'. At the time of writing, Twilio will try to infer the correct E.164 format if it is not used, but this should not be relied upon. """ number = models.CharField( max_length=30, help_text="The mobile number to deliver tokens to (E.164)." ) class Meta(SideChannelDevice.Meta): verbose_name = "Twilio SMS Device"
[docs] def get_throttle_factor(self): return settings.OTP_TWILIO_THROTTLE_FACTOR
[docs] def generate_challenge(self): """ Sends the current TOTP token to ``self.number``. :returns: :setting:`OTP_TWILIO_CHALLENGE_MESSAGE` on success. :raises: Exception if delivery fails. """ self.generate_token(valid_secs=settings.OTP_TWILIO_TOKEN_VALIDITY) message = settings.OTP_TWILIO_TOKEN_TEMPLATE.format(token=self.token) if settings.OTP_TWILIO_NO_DELIVERY: logger.info(message) else: self._deliver_token(message) challenge = settings.OTP_TWILIO_CHALLENGE_MESSAGE.format(token=self.token) return challenge
def _deliver_token(self, token): self._validate_config() url = '{0}/2010-04-01/Accounts/{1}/Messages.json'.format( settings.OTP_TWILIO_URL, settings.OTP_TWILIO_ACCOUNT ) data = { 'To': self.number, 'Body': str(token), } if settings.OTP_TWILIO_MESSAGING_SERVICE_SID: data['MessagingServiceSid'] = settings.OTP_TWILIO_MESSAGING_SERVICE_SID elif settings.OTP_TWILIO_FROM: data['From'] = settings.OTP_TWILIO_FROM else: raise ImproperlyConfigured( 'Either OTP_TWILIO_FROM or OTP_TWILIO_MESSAGING_SERVICE_SID must be set.' ) response = requests.post( url, data=data, auth=( settings.OTP_TWILIO_API_KEY if settings.OTP_TWILIO_API_KEY else settings.OTP_TWILIO_ACCOUNT, settings.OTP_TWILIO_AUTH, ), ) try: response.raise_for_status() except Exception as e: logger.exception('Error sending token by Twilio SMS: {0}'.format(e)) raise if 'sid' not in response.json(): message = response.json().get('message') logger.error('Error sending token by Twilio SMS: {0}'.format(message)) raise Exception(message) def _validate_config(self): if settings.OTP_TWILIO_ACCOUNT is None: raise ImproperlyConfigured( 'OTP_TWILIO_ACCOUNT must be set to your Twilio account identifier' ) if settings.OTP_TWILIO_AUTH is None: raise ImproperlyConfigured( 'OTP_TWILIO_AUTH must be set to your Twilio auth token' )
[docs] def verify_token(self, token): verify_allowed, _ = self.verify_is_allowed() if verify_allowed: verified = super().verify_token(token) if verified: self.throttle_reset() else: self.throttle_increment() else: verified = False return verified