This commit is contained in:
Sayyid Hamid Mahdavi 2025-11-20 13:57:23 +03:30
parent 0d59a34e06
commit 2fe322241a
6 changed files with 127 additions and 27 deletions

View file

@ -0,0 +1,99 @@
# Generated by Django 5.1.4 on 2025-11-20 09:05
import django.utils.timezone
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gooyal_oauth2', '0004_application_avatar'),
]
operations = [
migrations.AlterUniqueTogether(
name='refreshtoken',
unique_together=set(),
),
migrations.AddField(
model_name='application',
name='created_at',
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='application',
name='updated_at',
field=models.DateTimeField(auto_now=True, db_index=True),
),
migrations.AddField(
model_name='grant',
name='created_at',
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='grant',
name='updated_at',
field=models.DateTimeField(auto_now=True, db_index=True),
),
migrations.AddField(
model_name='refreshtoken',
name='created_at',
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='refreshtoken',
name='updated_at',
field=models.DateTimeField(auto_now=True, db_index=True),
),
migrations.AddField(
model_name='resource',
name='created_at',
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='resource',
name='updated_at',
field=models.DateTimeField(auto_now=True, db_index=True),
),
migrations.AddField(
model_name='scope',
name='created_at',
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='scope',
name='updated_at',
field=models.DateTimeField(auto_now=True, db_index=True),
),
migrations.AlterField(
model_name='application',
name='uuid',
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
),
migrations.AlterField(
model_name='grant',
name='uuid',
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
),
migrations.AlterField(
model_name='refreshtoken',
name='uuid',
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
),
migrations.AlterField(
model_name='resource',
name='uuid',
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
),
migrations.AlterField(
model_name='scope',
name='uuid',
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
),
]

View file

@ -16,14 +16,14 @@ from django.conf import settings
from django.db import models, router, transaction
from django.utils import timezone
from utils.models import BaseModel
from .settings import oauth2_settings
logger = logging.getLogger(__name__)
class Resource(models.Model):
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
class Resource(BaseModel):
name = models.CharField(max_length=255)
user = models.ForeignKey(
@ -36,13 +36,12 @@ class Resource(models.Model):
return self.name
class Application(AbstractApplication):
class Application(AbstractApplication, BaseModel):
"""
Application model for use with Django OAuth Toolkit that allows the scopes
available to an application to be restricted on a per-application basis.
"""
id=None
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
id = None
name = models.CharField(max_length=255, blank=False, unique=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
@ -74,14 +73,12 @@ class Application(AbstractApplication):
allowed_scopes = self.allowed_scopes
return Scope.objects.filter(name__in=allowed_scopes).order_by('name')
def allows_grant_type(self, *grant_types):
# Assume, for this example, that self.authorization_grant_type is set to self.GRANT_AUTHORIZATION_CODE
return bool(set([self.authorization_grant_type, self.GRANT_CLIENT_CREDENTIALS]) & set(grant_types))
class Scope(models.Model):
class Scope(BaseModel):
"""
Django model for an OAuth scope.
"""
@ -89,8 +86,6 @@ class Scope(models.Model):
# NOTE: This is not used to limit access to the scope in any way - we want the
# scope to be available to other applications in order to request access
# to the resource it protects!
id = None
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
application = models.ForeignKey(
oauth2_settings.APPLICATION_MODEL,
models.CASCADE,
@ -177,17 +172,15 @@ class Scope(models.Model):
return True
class Grant(AbstractGrant):
class Grant(AbstractGrant, BaseModel):
id = None
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
application = models.ForeignKey(
oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='grants'
)
class RefreshToken(AbstractRefreshToken):
class RefreshToken(BaseModel, AbstractRefreshToken):
id = None
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
application = models.ForeignKey(
oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='refresh_tokens')
@ -215,7 +208,6 @@ class RefreshToken(AbstractRefreshToken):
self.save()
class AccessToken(AbstractAccessToken):
id = None
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
@ -240,7 +232,7 @@ def clear_expired():
flat_queryset = queryset.values_list("pk", flat=True)[:CLEAR_EXPIRED_TOKENS_BATCH_SIZE]
batch_length = flat_queryset.count()
queryset.model.objects.filter(pk__in=list(flat_queryset)).delete()
logger.debug(f"{batch_length} tokens deleted, {current_no-batch_length} left")
logger.debug(f"{batch_length} tokens deleted, {current_no - batch_length} left")
queryset = queryset.model.objects.filter(query)
time.sleep(CLEAR_EXPIRED_TOKENS_BATCH_INTERVAL)
current_no = queryset.count()
@ -298,4 +290,3 @@ def clear_expired():
grants_deleted_no = batch_delete(grants, grants_query)
logger.info("%s Expired grant tokens deleted", grants_deleted_no)

View file

@ -31,3 +31,9 @@ class ApplicationViewSet(ReadOnlyModelViewSet):
else:
raise Http404()
# @action(detail=True, permission_classes=[AllowAny])
# def tokens(self, request, pk=None):
# app: Application = self.get_object()

View file

@ -2,7 +2,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
MAX_OTP_TRY = 3
DEVELOPMENT_PHONE_NUMBERS = ['+989999999999', '+989999999998']
DEVELOPMENT_PHONE_NUMBERS = []
MAX_OTP_VALID_DURATION = 120
class GenderChoices(models.TextChoices):

View file

@ -166,6 +166,7 @@ class User(AbstractUser):
self.otp_expire = timezone.now() + timedelta(seconds=MAX_OTP_VALID_DURATION)
self.otp_try = 0
self.save()
def set_ott(self):
self.ott = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32))
@ -180,18 +181,22 @@ class User(AbstractUser):
def check_otp(self, otp):
if self.otp_try <= MAX_OTP_TRY:
result = otp and check_password(self.otp, otp) and self.otp_is_valid()
try:
result = otp and check_password(otp, self.otp) and self.otp_is_valid()
except Exception as e:
result = False
if result:
self.otp = None
self.date_joined = timezone.now()
if not self.date_joined:
self.date_joined = timezone.now()
else:
self.otp_try += 1
self.save()
else:
self.otp = None
result = False
self.save()
return result
def check_ott(self, ott: str):

View file

@ -73,13 +73,12 @@ class RequestOTPSerializer(serializers.ModelSerializer):
try:
user.set_otp()
except Exception as e:
raise UnprocessableEntity()
user.save()
raise UnprocessableEntity('otp expire time not reached')
user.send_otp()
else:
raise UnprocessableEntity()
raise UnprocessableEntity('otp expire time not reached yet')
self.instance = user
return user