Source code for deux.services
from __future__ import absolute_import, unicode_literals
import six
from uuid import uuid4
from django.utils.crypto import constant_time_compare
from django_otp.oath import totp
from deux.app_settings import mfa_settings
from deux.constants import CHALLENGE_TYPES, SMS
[docs]def generate_mfa_code(bin_key, drift=0):
"""
Generates an MFA code based on the ``bin_key`` for the current timestamp
offset by the ``drift``.
:param bin_key: The secret key to be converted into an MFA code
:param drift: Number of time steps to shift the conversion.
"""
return six.text_type(totp(
bin_key,
step=mfa_settings.STEP_SIZE,
digits=mfa_settings.MFA_CODE_NUM_DIGITS,
drift=drift
)).zfill(mfa_settings.MFA_CODE_NUM_DIGITS)
[docs]def generate_key():
"""Generates a key used for secret keys."""
return uuid4().hex
[docs]def verify_mfa_code(bin_key, mfa_code):
"""
Verifies that the inputted ``mfa_code`` is a valid code for the given
secret key. We check the ``mfa_code`` against the current time stamp as
well as one time step before and after.
:param bin_key: The secret key to verify the MFA code again.
:param mfa_code: The code whose validity this function tests.
"""
if not mfa_code:
return False
try:
mfa_code = int(mfa_code)
except ValueError:
return False
else:
totp_check = lambda drift: int(
generate_mfa_code(bin_key=bin_key, drift=drift))
return any(
constant_time_compare(totp_check(drift), mfa_code)
for drift in [-1, 0, 1]
)
[docs]class MultiFactorChallenge(object):
"""
A class that represents a supported challenge and has the ability to
execute the challenge.
:param instance: :class:`MultiFactorAuth` instance to use.
:param challenge_type: Challenge type being used for this object.
:raises AssertionError: If ``challenge_type`` is not a supported
challenge type.
"""
def __init__(self, instance, challenge_type):
assert challenge_type in CHALLENGE_TYPES, (
"Inputted challenge type is not supported."
)
self.instance = instance
self.challenge_type = challenge_type
[docs] def generate_challenge(self):
"""
Generates and executes the challenge object based on the challenge
type of this object.
"""
dispatch = {
SMS: self._sms_challenge
}
for challenge in CHALLENGE_TYPES:
assert challenge in dispatch, (
"'{challenge}' does not have a challenge dispatch "
"method.".format(challenge=challenge)
)
return dispatch[self.challenge_type]()
def _sms_challenge(self):
"""Executes the SMS challenge."""
code = generate_mfa_code(bin_key=self.instance.sms_bin_key)
mfa_settings.SEND_MFA_TEXT_FUNC(
mfa_instance=self.instance, mfa_code=code)