from django.db import models from django.contrib.auth.models import AbstractUser from rest_framework import exceptions from rest_framework import authentication import json import logging import pyotp logger = logging.getLogger(__name__) class OTPSeed(AbstractUser): id = models.AutoField(primary_key=True) name = models.CharField(max_length=128) realm = models.CharField(max_length=128) seed = models.CharField(max_length=128) class Meta: unique_together = (('name', 'realm'),) def save(self, *args, **kwargs): """ inject username to ensure it stays unique / is setup at all """ if not self.is_superuser: self.username = "{}@{}".format(self.name, self.realm) else: self.name = self.username self.realm = "ungleich-admin" self.seed = pyotp.random_base32() super().save(*args, **kwargs) def __str__(self): return "'{}'@{} -- {}".format(self.name, self.realm, self.username) from otpauth.serializer import TokenSerializer class OTPAuthentication(authentication.BaseAuthentication): def authenticate(self, request): logger.debug("in authenticate {}".format(json.dumps(request.data))) serializer = TokenSerializer(data=request.data) if serializer.is_valid(): instance, token = serializer.save() else: logger.error("serializer is invalid") raise exceptions.AuthenticationFailed() # not dealing with admin realm -> can only be auth [see serializer] if not instance.realm == "ungleich-admin": if not request.path == "/ungleichotp/verify/": logger.debug("request.path is not /ungleichotp/verify/") raise exceptions.AuthenticationFailed() logger.debug("AUTH DONE: {} - {}".format(request.path, instance)) return (instance, token)