Implement additional security and functionality in authentication with the introduction of docstrings, type hints, and extended verification logic in the TOTP model to prevent repeated token use, improving robustness against replay attacks. Simultaneously, established the groundwork for RADIUS (Remote Authentication Dial-In User Service) support by creating models and management commands essential for handling authentication, accounting packets, and web-based authentication challenges, broadening the system's capability to integrate with network access servers and services. Resolves issues with token replay attacks and sets the stage for scalable network authentication mechanisms.
81 lines
2.7 KiB
Python
81 lines
2.7 KiB
Python
from django.db import models
|
|
from django.contrib.auth import get_user_model
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from cidrfield.models import IPNetworkField
|
|
|
|
|
|
class RadiusChallengeChoice(models.TextChoices):
|
|
NONE = "none", "None"
|
|
TOTP = "totp", "Time-based One-time Password"
|
|
WEB = "web", "Web-based"
|
|
|
|
class RadiusUser(models.Model):
|
|
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
|
|
challenge = models.CharField(
|
|
max_length=255, choices=RadiusChallengeChoice.choices, default=RadiusChallengeChoice.NONE
|
|
)
|
|
|
|
class RadiusNetwork(models.Model):
|
|
name = models.CharField(max_length=255)
|
|
network = IPNetworkField()
|
|
|
|
|
|
class RadiusUserIP(models.Model):
|
|
user = models.ForeignKey(RadiusUser, on_delete=models.CASCADE)
|
|
network = models.ForeignKey(RadiusNetwork, on_delete=models.CASCADE)
|
|
ipv4_address = models.GenericIPAddressField(protocol="ipv4", null=True, blank=True)
|
|
ipv6_prefix = models.GenericIPAddressField(protocol="ipv6", null=True, blank=True)
|
|
|
|
class Meta:
|
|
unique_together = ("user", "network")
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.ip_address in self.network.network:
|
|
raise ValueError(
|
|
f"IPv4 address {self.ip_address} not in network {self.network.network}"
|
|
)
|
|
if not self.ipv6_prefix in self.network.network:
|
|
raise ValueError(
|
|
f"IPv6 prefix {self.ipv6_prefix} not in network {self.network.network}"
|
|
)
|
|
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
class RadiusAccountingSession(models.Model):
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
|
|
user = models.ForeignKey(RadiusUser, on_delete=models.CASCADE)
|
|
ip = models.ForeignKey(RadiusUserIP, on_delete=models.CASCADE)
|
|
|
|
start_time = models.DateTimeField(null=True, blank=True)
|
|
stop_time = models.DateTimeField(null=True, blank=True)
|
|
|
|
def start(self):
|
|
self.start_time = datetime.now()
|
|
self.save()
|
|
|
|
def stop(self):
|
|
self.stop_time = datetime.now()
|
|
self.save()
|
|
|
|
|
|
class RadiusAccountingEvent(models.Model):
|
|
session = models.ForeignKey(RadiusAccountingSession, on_delete=models.CASCADE)
|
|
|
|
event_type = models.CharField(max_length=255)
|
|
event_time = models.DateTimeField(auto_now_add=True)
|
|
raw_data = models.TextField()
|
|
|
|
|
|
class RadiusWebAuthentication(models.Model):
|
|
user = models.ForeignKey(RadiusUser, on_delete=models.CASCADE)
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
expiry = models.DateTimeField()
|
|
ip = models.IPAddressField()
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.expiry = self.timestamp + timedelta(minutes=5)
|
|
super().save(*args, **kwargs) |