# Extending Django-OTP

A django-otp plugin is defined as a Django app that includes at least one model derived from `django_otp.models.Device`. All Device-derived model objects will be detected by the framework and included in the standard forms and APIs.

## Writing a Device

A `Device` subclass is only required to implement one method:

`Device.``verify_token`(token)

  Verifies a token. As a rule, the token should no longer be valid if this returns `True`.

  Parameters: | token (string) – The OTP token provided by the user.
  Return type: | bool

Most devices will also need to define one or more model fields to do anything interesting. Here’s a simple implementation of a generic TOTP device:

    from binascii import unhexlify

    from django.db import models

    from django_otp.models import Device
    from django_otp.oath import totp
    from django_otp.util import random_hex, hex_validator

    class TOTPDevice(Device):
        key = models.CharField(max_length=80,
                               validators=[hex_validator()],
                               default=lambda: random_hex(20),
                               help_text=u'A hex-encoded secret key of up to 40 bytes.')

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

        def verify_token(self, token):
            """
            Try to verify ``token`` against the current and previous TOTP value.
            """
            try:
                token = int(token)
            except ValueError:
                verified = False
            else:
                verified = any(totp(self.bin_key, drift=drift) == token for drift in [0, -1])

            return verified

This example also shows some of the low-level utilities django_otp provides for OATH and hex-encoded values.

If a device uses a challenge-response algorithm or requires some other kind of user interaction, it should implement an additional method:

`Device.``generate_challenge`()

  Generates a challenge value that the user will need to produce a token. This method is permitted to have side effects, such as transmitting information to the user through some other channel (email or SMS, perhaps). And, of course, some devices may need to commit the challenge to the databse.

  Returns: | A message to the user. This should be a string that fits comfortably in the template `'OTP Challenge: {0}'`. This may return `None` if this device is not interactive.
  Return type: | string or `None`
  Raises: | Any `Exception` is permitted. Callers should trap `Exception` and report it to the user.

## Utilities

django_otp provides several low-level utilities as a convenience to plugin implementors.

### django_otp.oath

`django_otp.oath.``hotp`(key, counter, digits=6)

  Implementation of the HOTP algorithm from RFC 4226.

  Parameters:

  - key (bytes) – The shared secret. A 20-byte string is recommended.
  - counter (int) – The password counter.
  - digits (int) – The number of decimal digits to generate.

  Returns: | The HOTP token.
  Return type: | int

      >>> key = b'12345678901234567890'
      >>> for c in range(10):
      ...     hotp(key, c)
      755224
      287082
      359152
      969429
      338314
      254676
      287922
      162583
      399871
      520489

`django_otp.oath.``totp`(key, step=30, t0=0, digits=6, drift=0)

  Implementation of the TOTP algorithm from RFC 6238.

  Parameters:

  - key (bytes) – The shared secret. A 20-byte string is recommended.
  - step (int) – The time step in seconds. The time-based code changes every `step` seconds.
  - t0 (int) – The Unix time at which to start counting time steps.
  - digits (int) – The number of decimal digits to generate.
  - drift (int) – The number of time steps to add or remove. Delays and clock differences might mean that you have to look back or forward a step or two in order to match a token.

  Returns: | The TOTP token.
  Return type: | int

      >>> key = b'12345678901234567890'
      >>> now = int(time())
      >>> for delta in range(0, 200, 20):
      ...     totp(key, t0=(now-delta))
      755224
      755224
      287082
      359152
      359152
      969429
      338314
      338314
      254676
      287922

class `django_otp.oath.``TOTP`(key, step=30, t0=0, digits=6, drift=0)

  An alternate TOTP interface.

  This provides access to intermediate steps of the computation. This is a living object: the return values of `t` and `token` will change along with other properties and with the passage of time.

  Parameters:

  - key (bytes) – The shared secret. A 20-byte string is recommended.
  - step (int) – The time step in seconds. The time-based code changes every `step` seconds.
  - t0 (int) – The Unix time at which to start counting time steps.
  - digits (int) – The number of decimal digits to generate.
  - drift (int) – The number of time steps to add or remove. Delays and clock differences might mean that you have to look back or forward a step or two in order to match a token.

      >>> key = b'12345678901234567890'
      >>> totp = TOTP(key)
      >>> totp.time = 0
      >>> totp.t()
      0
      >>> totp.token()
      755224
      >>> totp.time = 30
      >>> totp.t()
      1
      >>> totp.token()
      287082
      >>> totp.verify(287082)
      True
      >>> totp.verify(359152)
      False
      >>> totp.verify(359152, tolerance=1)
      True
      >>> totp.drift
      1
      >>> totp.drift = 0
      >>> totp.verify(359152, tolerance=1, min_t=3)
      False
      >>> totp.drift
      0
      >>> del totp.time
      >>> totp.t0 = int(time()) - 60
      >>> totp.t()
      2
      >>> totp.token()
      359152

  `t`()

    The computed time step.

  `time`

    The current time.

    By default, this returns time.time() each time it is accessed. If you want to generate a token at a specific time, you can set this property to a fixed value instead. Deleting the value returns it to its ‘live’ state.

  `token`()

    The computed TOTP token.

  `verify`(token, tolerance=0, min_t=None)

    A high-level verification helper.

    Parameters:

    - token (int) – The provided token.
    - tolerance (int) – The amount of clock drift you’re willing to accommodate, in steps. We’ll look for the token at t values in [t - tolerance, t + tolerance].
    - min_t (int) – The minimum t value we’ll accept. As a rule, this should be one larger than the largest t value of any previously accepted token.

    Return type: | bool

    Iff this returns True, self.drift will be updated to reflect the drift value that was necessary to match the token.

### django_otp.util

`django_otp.util.``hex_validator`(length=0)

  Returns a function to be used as a model validator for a hex-encoded CharField. This is useful for secret keys of all kinds:

      def key_validator(value):
          return hex_validator(20)(value)

      key = models.CharField(max_length=40, validators=[key_validator], help_text=u'A hex-encoded 20-byte secret key')

  Parameters: | length (int) – If greater than 0, validation will fail unless the decoded value is exactly this number of bytes.
  Return type: | function

      >>> hex_validator()('0123456789abcdef')
      >>> hex_validator(8)(b'0123456789abcdef')
      >>> hex_validator()('phlebotinum')
      Traceback (most recent call last):
          ...
      ValidationError: ['phlebotinum is not valid hex-encoded data.']
      >>> hex_validator(9)('0123456789abcdef')
      Traceback (most recent call last):
          ...
      ValidationError: ['0123456789abcdef does not represent exactly 9 bytes.']

`django_otp.util.``random_hex`(length=20)

  Returns a string of random bytes encoded as hex. This uses `os.urandom()`, so it should be suitable for generating cryptographic keys.

  Parameters: | length (int) – The number of (decoded) bytes to return.
  Returns: | A string of hex digits.
  Return type: | str