Ejemplo n.º 1
0
 def clean(self):
     try:
         hex_validator()(self.key)
         if self.number:
             phone_number_validator(self.number)
     except DjangoValidationError as e:
         raise ValidationError(e)
Ejemplo n.º 2
0
class PhoneDevice(Device):
    """
    Model with phone number and token seed linked to a user.
    """
    number = models.CharField(max_length=16,
                              validators=[phone_number_validator],
                              verbose_name=_('number'))
    key = models.CharField(max_length=40,
                           validators=[hex_validator()],
                           default=lambda: random_hex(20),
                           help_text="Hex-encoded secret key")
    method = models.CharField(max_length=4,
                              choices=PHONE_METHODS,
                              verbose_name=_('method'))

    @property
    def bin_key(self):
        return unhexlify(self.key.encode())

    def verify_token(self, token):
        for drift in range(-5, 1):
            if totp(self.bin_key, drift=drift) == token:
                return True
        return False

    def generate_challenge(self):
        """
        Sends the current TOTP token to `self.number` using `self.method`.
        """
        token = '%06d' % totp(self.bin_key)
        if self.method == 'call':
            make_call(device=self, token=token)
        else:
            send_sms(device=self, token=token)
Ejemplo n.º 3
0
class EmailDevice(Device):
    """
    A :class:`~django_otp.models.Device` that delivers a token to the user's
    registered email address (``user.email``). This is intended for
    demonstration purposes; if you allow users to reset their passwords via
    email, then this provides no security benefits.

    .. attribute:: key

        *CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20
        random bytes)
    """
    key = models.CharField(
        max_length=80,
        validators=[hex_validator()],
        default=lambda: random_hex(20),
        help_text='A hex-encoded secret key of up to 20 bytes.')

    @property
    def bin_key(self):
        return unhexlify(self.key.encode())

    def generate_challenge(self):
        token = totp(self.bin_key)
        body = render_to_string('otp/email/token.txt', {'token': token})

        send_mail(settings.OTP_EMAIL_SUBJECT, body, settings.OTP_EMAIL_SENDER,
                  [self.user.email])

        message = "sent by email"

        return message

    def verify_token(self, token):
        try:
            token = int(token)
        except Exception:
            verified = False
        else:
            verified = any(
                totp(self.bin_key, drift=drift) == token for drift in [0, -1])

        return verified
Ejemplo n.º 4
0
def key_validator(*args, **kwargs):
    """Wraps hex_validator generator, to keep makemigrations happy."""
    return hex_validator()(*args, **kwargs)
Ejemplo n.º 5
0
def key_validator(value):
    return hex_validator()(value)
Ejemplo n.º 6
0
def key_validator(value):
    return hex_validator()(value)
Ejemplo n.º 7
0
def id_validator(value):
    return hex_validator(6)(value)
Ejemplo n.º 8
0
def key_validator(*args, **kwargs):
    """Wraps hex_validator generator, to keep makemigrations happy."""
    return hex_validator()(*args, **kwargs)
Ejemplo n.º 9
0
def key_validator(value):  # pragma: no cover
    """ Obsolete code here for migrations. """
    return hex_validator()(value)
Ejemplo n.º 10
0
def token_validator(*args, **kwargs):
    """ Wraps hex_validator generator satisfying `makemigrations` """
    return hex_validator()(*args, **kwargs)
Ejemplo n.º 11
0
class TOTPDevice(Device):
    """
    A generic TOTP :class:`~django_otp.models.Device`. The model fields mostly
    correspond to the arguments to :func:`django_otp.oath.totp`. They all have
    sensible defaults, including the key, which is randomly generated.

    .. attribute:: key

        *CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20
        random bytes)

    .. attribute:: step

        *PositiveSmallIntegerField*: The time step in seconds. (Default: 30)

    .. attribute:: t0

        *BigIntegerField*: The Unix time at which to begin counting steps.
        (Default: 0)

    .. attribute:: digits

        *PositiveSmallIntegerField*: The number of digits to expect in a token
        (6 or 8).  (Default: 6)

    .. attribute:: tolerance

        *PositiveSmallIntegerField*: The number of time steps in the past or
        future to allow. For example, if this is 1, we'll accept any of three
        tokens: the current one, the previous one, and the next one. (Default:
        1)

    .. attribute:: drift

        *SmallIntegerField*: The number of time steps the prover is known to
        deviate from our clock.  If :setting:`OTP_TOTP_SYNC` is ``True``, we'll
        update this any time we match a token that is not the current one.
        (Default: 0)

    .. attribute:: last_t

        *BigIntegerField*: The time step of the last verified token. To avoid
        verifying the same token twice, this will be updated on each successful
        verification. Only tokens at a higher time step will be verified
        subsequently. (Default: -1)

    """
    key = models.CharField(
        max_length=80,
        validators=[hex_validator()],
        default=lambda: random_hex(20),
        help_text="A hex-encoded secret key of up to 40 bytes.")
    step = models.PositiveSmallIntegerField(
        default=30, help_text="The time step in seconds.")
    t0 = models.BigIntegerField(
        default=0, help_text="The Unix time at which to begin counting steps.")
    digits = models.PositiveSmallIntegerField(
        choices=[(6, 6), (8, 8)],
        default=6,
        help_text="The number of digits to expect in a token.")
    tolerance = models.PositiveSmallIntegerField(
        default=1,
        help_text="The number of time steps in the past or future to allow.")
    drift = models.SmallIntegerField(
        default=0,
        help_text=
        "The number of time steps the prover is known to deviate from our clock."
    )
    last_t = models.BigIntegerField(
        default=-1,
        help_text=
        "The t value of the latest verified token. The next token must be at a higher time step."
    )

    class Meta(Device.Meta):
        verbose_name = "TOTP device"

    @property
    def bin_key(self):
        """
        The secret key as a binary string.
        """
        return unhexlify(self.key.encode())

    def verify_token(self, token):
        OTP_TOTP_SYNC = getattr(settings, 'OTP_TOTP_SYNC', True)

        try:
            token = int(token)
        except Exception:
            verified = False
        else:
            key = self.bin_key

            totp = TOTP(key, self.step, self.t0, self.digits)
            totp.time = time.time()

            for offset in range(-self.tolerance, self.tolerance + 1):
                totp.drift = self.drift + offset
                if (totp.t() > self.last_t) and (totp.token() == token):
                    self.last_t = totp.t()
                    if (offset != 0) and OTP_TOTP_SYNC:
                        self.drift += offset
                    self.save()

                    verified = True
                    break
            else:
                verified = False

        return verified
Ejemplo n.º 12
0
class HOTPDevice(Device):
    """
    A generic HOTP :class:`~django_otp.models.Device`. The model fields mostly
    correspond to the arguments to :func:`django_otp.oath.hotp`. They all have
    sensible defaults, including the key, which is randomly generated.

    .. attribute:: key

        *CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20
        random bytes)

    .. attribute:: digits

        *PositiveSmallIntegerField*: The number of digits to expect from the
        token generator (6 or 8). (Default: 6)

    .. attribute:: tolerance

        *PositiveSmallIntegerField*: The number of missed tokens to tolerate.
        (Default: 5)

    .. attribute:: counter

        *BigIntegerField*: The next counter value to expect. (Initial: 0)
    """
    key = models.CharField(
        max_length=80,
        validators=[hex_validator()],
        default=lambda: random_hex(20),
        help_text="A hex-encoded secret key of up to 40 bytes.")
    digits = models.PositiveSmallIntegerField(
        choices=[(6, 6), (8, 8)],
        default=6,
        help_text="The number of digits to expect in a token.")
    tolerance = models.PositiveSmallIntegerField(
        default=5, help_text="The number of missed tokens to tolerate.")
    counter = models.BigIntegerField(
        default=0, help_text="The next counter value to expect.")

    class Meta(Device.Meta):
        verbose_name = "HOTP device"

    @property
    def bin_key(self):
        """
        The secret key as a binary string.
        """
        return unhexlify(self.key.encode())

    def verify_token(self, token):
        try:
            token = int(token)
        except Exception:
            verified = False
        else:
            key = self.bin_key

            for counter in range(self.counter,
                                 self.counter + self.tolerance + 1):
                if hotp(key, counter, self.digits) == token:
                    verified = True
                    self.counter = counter + 1
                    self.save()
                    break
            else:
                verified = False

        return verified