add product

This commit is contained in:
javad committed 2026-10-08 16:37:45 +03:30
1 parent 423d4cf40e
commit c8d45dd07c
58 files changed
+8309 -674

No files matched your search

+34 -12
View File
@@ -1,17 +1,39 @@
from django.contrib import admin
from apps.jwt_auth.models import (
JwtToken as JwtTokenModel,
CUserRoleLoginOpt as CUserRoleLoginOptModel,
)
# Register your models here.
from apps.jwt_auth.models import JwtToken, CUserRoleLoginOpt
from utils.admin_base import CRMModelAdmin
@admin.register(JwtTokenModel)
class JwtTokenAdmin(admin.ModelAdmin):
pass
@admin.register(JwtToken)
class JwtTokenAdmin(CRMModelAdmin):
list_display = ('id', 'cuser', 'cuser_role', 'expire', 'is_blocked')
list_filter = ('is_blocked', 'cuser_role__sys_ws')
search_fields = ('cuser__username', 'cuser__mobile')
exclude = ('refresh', 'access')
sensitive_fields = ('refresh', 'access')
readonly_fields = ('cuser', 'cuser_role', 'expire', 'is_blocked')
actions = ('revoke_sessions', 'export_selected')
@admin.action(description='ابطال نشست‌های انتخاب‌شده', permissions=['change'])
def revoke_sessions(self, request, queryset):
count = queryset.update(is_blocked=True)
for obj in queryset:
self.log_change(request, obj, 'نشست از پنل مدیریت ابطال شد.')
self.message_user(request, f'{count} نشست ابطال شد.')
def has_add_permission(self, request):
return False
@admin.register(CUserRoleLoginOptModel)
class CUserRoleLoginOptAdmin(admin.ModelAdmin):
pass
@admin.register(CUserRoleLoginOpt)
class CUserRoleLoginOptAdmin(CRMModelAdmin):
list_display = ('id', 'cuser', 'cuser_role', 'expire')
search_fields = ('cuser__username', 'cuser__mobile')
exclude = ('uuid',)
sensitive_fields = ('uuid',)
readonly_fields = ('cuser', 'cuser_role', 'expire')
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
+19
View File
@@ -0,0 +1,19 @@
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.authentication import JWTAuthentication
from apps.jwt_auth.models import JwtToken
from apps.jwt_auth.session import validate_session_claims
class SessionJWTAuthentication(JWTAuthentication):
"""A signed JWT is usable only while its recorded session remains active."""
def get_user(self, validated_token):
user = super().get_user(validated_token)
session = JwtToken.objects.select_related('cuser_role__role_type', 'cuser_role__sys_ws', 'cuser_role__cuser').filter(
access=str(validated_token), cuser=user, is_blocked=False,
).first()
if not session:
raise AuthenticationFailed('نشست شما معتبر نیست. لطفاً دوباره وارد شوید.', code='session_invalid')
validate_session_claims(session, validated_token)
return user
@@ -0,0 +1,12 @@
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [('jwt_auth', '0092_alter_cuserroleloginopt_uuid')]
operations = [
migrations.AlterField(
model_name='cuserroleloginopt', name='uuid',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
+1 -1
View File
@@ -22,4 +22,4 @@ class CUserRoleLoginOpt(models.Model):
cuser_role = models.ForeignKey(CuserRoleModel, on_delete=models.CASCADE)
cuser = models.ForeignKey(CUser, null=True, blank=True, default=None, on_delete=models.CASCADE)
expire = models.DateTimeField(null=False, blank=False)
uuid = models.UUIDField(default=uuid.uuid4(), unique=True, null=False, blank=False)
uuid = models.UUIDField(default=uuid.uuid4, unique=True, null=False, blank=False)
+87 -98
View File
@@ -1,110 +1,99 @@
import uuid
import random
import secrets
from datetime import timedelta
from django.contrib.auth import get_user_model
from apps.jwt_auth.models import CUserRoleLoginOpt
from apps.cuser.models import CuserRole as CuserRoleModel, SmsOpt as SmsOptModel
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import transaction
from django.utils import timezone
from rest_framework import exceptions
import requests
from datetime import timedelta
from apps.cuser.models import CuserRole, SmsOpt
from apps.jwt_auth.models import CUserRoleLoginOpt
from utils.services.sms import DefaultSmsProvider
from utils.validations.mobile import normalize_mobile
CUser = get_user_model()
def login_by_username(username: str, password: str) -> CUserRoleLoginOpt:
class SmsDeliveryUnavailable(exceptions.APIException):
status_code = 503
default_detail = 'ارسال پیامک انجام نشد. لطفاً کمی بعد دوباره تلاش کنید.'
default_code = 'sms_failed'
def eligible_roles(user, customer_only=False):
roles = CuserRole.objects.select_related('role_type', 'sys_ws', 'cuser').filter(
cuser=user, is_active=True, mobile_verify=True, sys_ws__is_active=True,
)
if customer_only:
roles = roles.filter(role_type__role_name_en='workspace_customer', customer_panel=True)
return [role for role in roles if
(not role.sys_ws.expire or role.sys_ws.expire > timezone.now()) and
(role.role_type.role_name_en != 'workspace_customer' or role.customer_panel is True)]
def create_login_options(user, roles):
CUserRoleLoginOpt.objects.filter(cuser=user).delete()
options = []
for role in roles:
options.append(CUserRoleLoginOpt.objects.create(
cuser=user, cuser_role=role, expire=timezone.now() + timedelta(minutes=5),
))
return options
@transaction.atomic
def login_by_username(username: str, password: str):
user = CUser.objects.select_for_update().filter(username=username).first()
if not user or not user.check_password(password):
raise exceptions.ValidationError({'error': 'نام کاربری یا رمز عبور اشتباه است.'})
if not user.is_active:
raise exceptions.PermissionDenied('حساب کاربری شما غیرفعال است.')
roles = eligible_roles(user)
if not roles:
raise exceptions.PermissionDenied('نقش یا دسترسی فعال برای این حساب وجود ندارد.')
return create_login_options(user, roles)
def mobile_user(mobile):
try:
print(username)
user = CUser.objects.get(username=username)
if user and user.check_password(password):
cuser_roles = CuserRoleModel.objects.filter(cuser=user)
CUserRoleLoginOpt.objects.filter(cuser_role__cuser=user).delete()
if cuser_roles:
for user_role_obj in cuser_roles:
if not (user_role_obj.is_active and user_role_obj.mobile_verify):
raise exceptions.ValidationError({"error": 'Not a valid user'})
expire_datetime = timezone.datetime.now() + timezone.timedelta(minutes=5)
user_role_opt = CUserRoleLoginOpt()
user_role_opt.cuser_role = user_role_obj
user_role_opt.cuser = user_role_obj.cuser
user_role_opt.expire = expire_datetime
user_role_opt.uuid = uuid.uuid4()
user_role_opt.save()
login_opts = CUserRoleLoginOpt.objects.filter(cuser_role__cuser=user)
return login_opts
else:
raise exceptions.ValidationError({'error': "User not have role"})
else:
raise exceptions.ValidationError({'error': "Username or password is not correct"})
except CUser.DoesNotExist:
raise exceptions.ValidationError({'error': "Username or password is not correct"})
mobile = normalize_mobile(mobile)
except DjangoValidationError as error:
raise exceptions.ValidationError(error.message_dict)
user = CUser.objects.filter(mobile=mobile).first()
if not user:
raise exceptions.ValidationError({'mobile': 'کاربری با این شماره موبایل ثبت نشده است.'})
if not user.is_active:
raise exceptions.PermissionDenied('حساب کاربری شما غیرفعال است.')
roles = eligible_roles(user, customer_only=True)
if not roles:
raise exceptions.PermissionDenied('دسترسی شما به پنل مشتریان فعال نیست.')
return user, roles
@transaction.atomic
def login_by_mobile(mobile: int):
try:
exists_cuser = CUser.objects.get(mobile=mobile)
random_number = random.randint(100000, 999999)
user, _ = mobile_user(mobile)
# Serialize code requests for this account, retaining the old valid code if
# the provider refuses the new message.
CUser.objects.select_for_update().get(pk=user.pk)
code = secrets.randbelow(900000) + 100000
if not DefaultSmsProvider().send_sms(str(user.mobile), f'کد ورود شما: {code}'):
raise SmsDeliveryUnavailable()
SmsOpt.objects.filter(mobile=user.mobile).delete()
SmsOpt.objects.create(mobile=user.mobile, opt=code, expire=timezone.now() + timedelta(minutes=3))
return user.mobile
try:
SmsOptModel.objects.filter(mobile=mobile).delete()
except Exception as exp:
pass
expire_time = timezone.now() + timedelta(minutes=3)
opt_code = SmsOptModel(mobile=mobile, opt=random_number, expire=expire_time)
opt_code.save()
sms_text = f' کد ورود شما: {random_number}'
payload = {
'USERNAME': 'vsms7937',
'PASSWORD': '335577775533',
'FROM': '30005367',
'TO': int(mobile),
'TEXT': f'{sms_text}'
}
r = requests.get('http://www.0098sms.com/sendsmslink.aspx', params=payload)
except Exception as exp:
raise exceptions.ValidationError({'error': "mobile not found"})
def verify_mobile_and_get_token(mobile: int, opt: int) -> CUserRoleLoginOpt:
try:
time_now = timezone.now()
opt_obj = SmsOptModel.objects.filter(mobile=mobile, opt=opt, expire__gte=time_now)[0]
if opt_obj:
cuser_roles = CuserRoleModel.objects.filter(cuser__mobile=mobile, is_active=True)
CUserRoleLoginOpt.objects.filter(cuser_role__in=cuser_roles).delete()
if cuser_roles:
for user_role_obj in cuser_roles:
expire_datetime = timezone.datetime.now() + timezone.timedelta(minutes=5)
user_role_opt = CUserRoleLoginOpt()
user_role_opt.cuser_role = user_role_obj
user_role_opt.cuser = user_role_obj.cuser
user_role_opt.expire = expire_datetime
user_role_opt.uuid = uuid.uuid4()
user_role_opt.save()
login_opts = CUserRoleLoginOpt.objects.filter(cuser_role__in=cuser_roles)
SmsOptModel.objects.filter(mobile=mobile).delete()
return login_opts
except Exception as exp:
try:
SmsOptModel.objects.filter(mobile=mobile).delete()
except Exception as exp:
pass
raise exceptions.ValidationError({'error': "cannot verify this mobile and opt code:"+str(exp)})
@transaction.atomic
def verify_mobile_and_get_token(mobile: int, opt: int):
user, roles = mobile_user(mobile)
CUser.objects.select_for_update().get(pk=user.pk)
code = SmsOpt.objects.select_for_update().filter(
mobile=user.mobile, opt=opt, expire__gt=timezone.now(),
).first()
if not code:
raise exceptions.ValidationError({'opt': 'کد تأیید اشتباه است یا اعتبار آن تمام شده است.'})
options = create_login_options(user, roles)
SmsOpt.objects.filter(mobile=user.mobile).delete()
return options
+49
View File
@@ -0,0 +1,49 @@
from datetime import datetime, timezone as datetime_timezone
from django.utils import timezone
from django.db import transaction
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.tokens import RefreshToken
from apps.jwt_auth.models import JwtToken
def validate_role_access(role):
if not (role.cuser and role.cuser.is_active and role.is_active and role.mobile_verify):
raise AuthenticationFailed('حساب کاربری یا نقش شما غیرفعال است. دوباره وارد شوید.', code='session_invalid')
workspace = role.sys_ws
if not workspace or not workspace.is_active or (workspace.expire and workspace.expire <= timezone.now()):
raise AuthenticationFailed('دسترسی فضای کاری غیرفعال یا منقضی شده است.', code='session_invalid')
if role.role_type.role_name_en == 'workspace_customer' and role.customer_panel is not True:
raise AuthenticationFailed('دسترسی شما به پنل مشتریان فعال نیست.', code='session_invalid')
@transaction.atomic
def issue_tokens(role, session=None):
validate_role_access(role)
if session is None:
session = JwtToken.objects.create(cuser=role.cuser, cuser_role=role, refresh='')
refresh = RefreshToken.for_user(role.cuser)
refresh['workspace_uuid'] = str(role.sys_ws.uuid)
refresh['cuser_role_uuid'] = str(role.uuid)
refresh['user_uuid'] = str(role.cuser.uuid)
refresh['session_id'] = session.pk
data = {'refresh': str(refresh), 'access': str(refresh.access_token)}
session.refresh = data['refresh']
session.access = data['access']
session.expire = datetime.fromtimestamp(refresh['exp'], tz=datetime_timezone.utc)
session.save()
return data
def validate_session_claims(session, token):
if session.expire and session.expire <= timezone.now():
raise AuthenticationFailed('نشست شما منقضی شده است. دوباره وارد شوید.', code='session_invalid')
role = session.cuser_role
validate_role_access(role)
expected = {'user_id': session.cuser_id, 'user_uuid': str(role.cuser.uuid),
'cuser_role_uuid': str(role.uuid), 'workspace_uuid': str(role.sys_ws.uuid)}
if token.get('session_id') is not None:
expected['session_id'] = session.pk
if role.cuser_id != session.cuser_id or any(token.get(key) != value for key, value in expected.items()):
raise AuthenticationFailed('نشست شما معتبر نیست. دوباره وارد شوید.', code='session_invalid')
+453
View File
@@ -0,0 +1,453 @@
"""Authentication, duplicate mobiles and ordering integration regressions."""
from datetime import timedelta
from io import BytesIO
from unittest.mock import Mock, patch
from django.contrib import admin
from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction
from django.utils import timezone
from openpyxl import load_workbook
from rest_framework.test import APITestCase
from rest_framework_simplejwt.tokens import RefreshToken
from apps.cuser.cuser_model import CUser
from apps.cuser.models import CuserRole, RoleType, SmsOpt
from apps.customer.models import Customer, ResponsibleGroup, ShiveAshenaei
from apps.jwt_auth.models import CUserRoleLoginOpt, JwtToken
from apps.jwt_auth.session import issue_tokens
from apps.shop.admin import ProductAdmin, ProductCategoryAdmin, excel_response
from apps.shop.models import Product, ProductCategory, Order, OrderItem
from apps.workspace.models import City, Workspace
from utils.services.sms import DosefrNavadHashtSMS
from utils.validations.mobile import normalize_mobile
class RegressionFixtures(APITestCase):
@classmethod
def setUpTestData(cls):
cls.workspace = Workspace.objects.create(name='Test', username='test')
cls.other_workspace = Workspace.objects.create(name='Other', username='other')
cls.user = CUser.objects.create_user('client', 'password', mobile=9123456789, name_family='Client', is_active=True)
cls.customer_type = RoleType.objects.create(role_name='Customer', role_name_en='workspace_customer')
cls.operator_type = RoleType.objects.create(role_name='Operator', role_name_en='workspace_operator')
cls.admin_type = RoleType.objects.create(role_name='Admin', role_name_en='workspace_admin')
cls.role = CuserRole.objects.create(cuser=cls.user, role_type=cls.customer_type, sys_ws=cls.workspace,
name_family='Client', mobile=cls.user.mobile, is_active=True,
mobile_verify=True, customer_panel=True)
cls.operator = CuserRole.objects.create(cuser=cls.user, role_type=cls.operator_type, sys_ws=cls.workspace,
name_family='Operator', mobile=cls.user.mobile, is_active=True, mobile_verify=True)
cls.customer = Customer.objects.create(name_family='Client', mobile=cls.user.mobile, cuser_role=cls.role, sys_ws=cls.workspace)
cls.city = City.objects.create(name='City')
cls.group = ResponsibleGroup.objects.create(name='Group', sys_ws=cls.workspace)
cls.shive = ShiveAshenaei.objects.create(name='Web', sys_ws=cls.workspace)
def use_role(self, role=None):
tokens = issue_tokens(role or self.role)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + tokens['access'])
return tokens
def post(self, path, data):
return self.client.post('/auth/' + path, data, format='json')
def otp(self, expired=False):
return SmsOpt.objects.create(mobile=self.user.mobile, opt=123456,
expire=timezone.now() + timedelta(minutes=-1 if expired else 3))
class AuthRegressionTests(RegressionFixtures):
def test_unknown_mobile_has_useful_400(self):
response = self.post('login/mobile/', {'mobile': '09999999999'})
self.assertEqual(response.status_code, 400)
self.assertIn('ثبت نشده', str(response.data))
def test_invalid_mobile_has_400(self):
self.assertEqual(self.post('login/mobile/', {'mobile': '123'}).status_code, 400)
def test_disabled_customer_panel_has_403_and_sends_no_sms(self):
self.role.customer_panel = False
self.role.save()
with patch('apps.jwt_auth.services.DefaultSmsProvider.send_sms') as send:
response = self.post('login/mobile/', {'mobile': self.user.mobile})
self.assertEqual(response.status_code, 403)
self.assertIn('پنل مشتریان', str(response.data))
send.assert_not_called()
def test_inactive_user_has_403(self):
self.user.is_active = False
self.user.save()
self.assertEqual(self.post('login/mobile/', {'mobile': self.user.mobile}).status_code, 403)
def test_sms_failure_has_503_and_preserves_old_otp(self):
old = self.otp()
with patch('apps.jwt_auth.services.DefaultSmsProvider.send_sms', return_value=False):
response = self.post('login/mobile/', {'mobile': self.user.mobile})
self.assertEqual(response.status_code, 503)
self.assertIn('ارسال پیامک', str(response.data))
self.assertTrue(SmsOpt.objects.filter(pk=old.pk).exists())
def test_successful_sms_saves_only_one_code(self):
self.otp()
with patch('apps.jwt_auth.services.DefaultSmsProvider.send_sms', return_value=True):
response = self.post('login/mobile/', {'mobile': '+98 912-345-6789'})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['result']['mobile'], self.user.mobile)
self.assertEqual(SmsOpt.objects.count(), 1)
self.assertGreaterEqual(SmsOpt.objects.get().opt, 100000)
def test_wrong_otp_preserves_valid_code(self):
old = self.otp()
self.assertEqual(self.post('login/mobile/verify/', {'mobile': self.user.mobile, 'opt': 654321}).status_code, 400)
self.assertTrue(SmsOpt.objects.filter(pk=old.pk).exists())
def test_expired_otp_rejected(self):
self.otp(expired=True)
self.assertEqual(self.post('login/mobile/verify/', {'mobile': self.user.mobile, 'opt': 123456}).status_code, 400)
def test_otp_is_single_use_and_roles_have_distinct_options(self):
CuserRole.objects.create(cuser=self.user, role_type=self.customer_type, sys_ws=self.other_workspace,
name_family='Other', mobile=self.user.mobile, is_active=True, mobile_verify=True, customer_panel=True)
self.otp()
response = self.post('login/mobile/verify/', {'mobile': self.user.mobile, 'opt': 123456})
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 2)
self.assertNotEqual(response.data[0]['uuid'], response.data[1]['uuid'])
self.assertEqual(self.post('login/mobile/verify/', {'mobile': self.user.mobile, 'opt': 123456}).status_code, 400)
def test_bad_password_has_400(self):
self.assertEqual(self.post('login/username/', {'username': 'client', 'password': 'wrong'}).status_code, 400)
def test_public_login_ignores_stale_authorization(self):
self.client.credentials(HTTP_AUTHORIZATION='Bearer invalid')
response = self.post('login/username/', {'username': 'client', 'password': 'password'})
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 2)
def test_option_expiry_and_replay_have_401(self):
option = CUserRoleLoginOpt.objects.create(cuser=self.user, cuser_role=self.role, expire=timezone.now()-timedelta(seconds=1))
self.assertEqual(self.post('token/', {'uuid_opt': str(option.uuid)}).status_code, 401)
option.expire = timezone.now()+timedelta(minutes=5)
option.save()
result = self.post('token/', {'uuid_opt': str(option.uuid)})
self.assertEqual(result.status_code, 200)
self.assertEqual(self.post('token/', {'uuid_opt': str(option.uuid)}).status_code, 401)
self.assertEqual(JwtToken.objects.count(), 1)
def test_anonymous_info_has_401(self):
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
def test_signed_but_unrecorded_access_is_rejected(self):
refresh = RefreshToken.for_user(self.user)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + str(refresh.access_token))
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
def test_blocked_access_and_refresh_are_rejected(self):
tokens = self.use_role()
JwtToken.objects.update(is_blocked=True)
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
self.assertEqual(self.post('token/refresh/', {'refresh': tokens['refresh']}).status_code, 401)
def test_missing_session_rejects_access_and_refresh(self):
tokens = self.use_role()
JwtToken.objects.all().delete()
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
self.assertEqual(self.post('token/refresh/', {'refresh': tokens['refresh']}).status_code, 401)
def test_refresh_rotates_and_retains_workspace(self):
tokens = self.use_role(self.operator)
response = self.post('token/refresh/', {'refresh': tokens['refresh']})
self.assertEqual(response.status_code, 200)
self.assertNotEqual(response.data['refresh'], tokens['refresh'])
self.assertEqual(RefreshToken(response.data['refresh'])['workspace_uuid'], str(self.workspace.uuid))
self.assertEqual(JwtToken.objects.count(), 1)
self.assertGreater(JwtToken.objects.get().expire, timezone.now())
self.assertEqual(self.post('token/refresh/', {'refresh': tokens['refresh']}).status_code, 401)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + response.data['access'])
self.assertEqual(self.client.get('/cuser/info/').status_code, 200)
def test_disabling_panel_revokes_both_tokens(self):
tokens = self.use_role()
self.role.customer_panel = False
self.role.save()
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
self.assertEqual(self.post('token/refresh/', {'refresh': tokens['refresh']}).status_code, 401)
def test_expired_workspace_revokes_both_tokens(self):
tokens = self.use_role()
self.workspace.expire = timezone.now()-timedelta(seconds=1)
self.workspace.save()
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
self.assertEqual(self.post('token/refresh/', {'refresh': tokens['refresh']}).status_code, 401)
def test_logout_revokes_session_without_valid_access(self):
tokens = self.use_role()
self.client.credentials(HTTP_AUTHORIZATION='Bearer expired')
self.assertEqual(self.post('logout/', {'refresh': tokens['refresh']}).status_code, 204)
self.assertEqual(self.post('token/refresh/', {'refresh': tokens['refresh']}).status_code, 401)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + tokens['access'])
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
self.assertEqual(self.post('logout/', {'refresh': tokens['refresh']}).status_code, 204)
def test_user_duplicate_formats_are_rejected(self):
for mobile in ['09123456789', '+98 912-345-6789', '۰۹۱۲۳۴۵۶۷۸۹', '00989123456789']:
with self.subTest(mobile=mobile), self.assertRaises(ValidationError):
CUser.objects.create_user('duplicate', 'password', mobile=mobile)
self.assertEqual(CUser.objects.count(), 1)
def test_customer_duplicate_has_database_constraint(self):
with self.assertRaises(IntegrityError), transaction.atomic():
Customer.objects.create(name_family='Duplicate', mobile=self.user.mobile, sys_ws=self.workspace)
Customer.objects.create(name_family='Other', mobile=self.user.mobile, sys_ws=self.other_workspace)
Customer.objects.create(name_family='Null1', sys_ws=self.workspace)
Customer.objects.create(name_family='Null2', sys_ws=self.workspace)
def test_create_customer_v2_duplicate_has_mobile_error(self):
self.use_role(self.operator)
response = self.client.post('/customer/v2/create/', {
'name_family': 'Duplicate', 'mobile': '+98 912-345-6789', 'phone': 2133333333,
'address': 'Address', 'city': self.city.pk, 'shive_ashenaei': self.shive.pk, 'responsible_group': self.group.pk,
}, format='json')
self.assertEqual(response.status_code, 400)
self.assertIn('mobile', response.data)
self.assertEqual(Customer.objects.count(), 1)
def test_partial_customer_update_rejects_duplicate_and_allows_same_mobile(self):
other = Customer.objects.create(name_family='Other', mobile=9333333333, sys_ws=self.workspace)
self.use_role(self.operator)
response = self.client.patch(f'/customer/update/{other.pk}/', {'mobile': '09123456789'}, format='json')
self.assertEqual(response.status_code, 400)
self.assertIn('mobile', response.data)
response = self.client.patch(f'/customer/update/{self.customer.pk}/', {'mobile': self.user.mobile}, format='json')
self.assertEqual(response.status_code, 200)
def test_panel_link_is_scoped_to_workspace(self):
other_operator = CuserRole.objects.create(cuser=self.user, role_type=self.operator_type, sys_ws=self.other_workspace,
name_family='Operator2', mobile=self.user.mobile, is_active=True, mobile_verify=True)
other_customer = Customer.objects.create(name_family='Other', mobile=self.user.mobile, sys_ws=self.other_workspace)
self.use_role(other_operator)
response = self.client.post('/customer/customer-panel/', {'customer_id': other_customer.pk, 'mobile': self.user.mobile, 'status': True}, format='json')
self.assertEqual(response.status_code, 200)
other_customer.refresh_from_db()
self.assertNotEqual(other_customer.cuser_role_id, self.role.pk)
self.assertEqual(other_customer.cuser_role.sys_ws_id, self.other_workspace.pk)
def test_disable_panel_without_link_does_not_create_user(self):
other = Customer.objects.create(name_family='Other', mobile=9333333333, sys_ws=self.workspace)
self.use_role(self.operator)
response = self.client.post('/customer/customer-panel/', {'customer_id': other.pk, 'mobile': other.mobile, 'status': False}, format='json')
self.assertEqual(response.status_code, 200)
self.assertIsNone(response.data['cuser_role_id'])
self.assertEqual(CUser.objects.count(), 1)
def test_provider_checks_body_and_handles_timeout(self):
import requests
with patch('utils.services.sms.requests.get', return_value=Mock(text='0')) as send:
self.assertTrue(DosefrNavadHashtSMS().send_sms(str(self.user.mobile), 'Test'))
self.assertEqual(send.call_args.kwargs['timeout'], 10)
with patch('utils.services.sms.requests.get', return_value=Mock(text='error')):
self.assertFalse(DosefrNavadHashtSMS().send_sms(str(self.user.mobile), 'Test'))
with patch('utils.services.sms.requests.get', side_effect=requests.Timeout):
self.assertFalse(DosefrNavadHashtSMS().send_sms(str(self.user.mobile), 'Test'))
def test_iranian_mobile_prefix_is_not_accidentally_removed(self):
self.assertEqual(normalize_mobile('9891234567'), 9891234567)
def test_logout_during_refresh_rotation_revokes_new_tokens(self):
tokens = self.use_role()
rotated = self.post('token/refresh/', {'refresh': tokens['refresh']})
self.assertEqual(rotated.status_code, 200)
self.assertEqual(self.post('logout/', {'refresh': tokens['refresh']}).status_code, 204)
self.assertEqual(self.post('token/refresh/', {'refresh': rotated.data['refresh']}).status_code, 401)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + rotated.data['access'])
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
def test_mobile_change_disconnects_old_panel_and_revokes_old_session(self):
tokens = issue_tokens(self.role)
self.use_role(self.operator)
response = self.client.patch(f'/customer/update/{self.customer.pk}/', {'mobile': '09333333333'}, format='json')
self.assertEqual(response.status_code, 200)
self.customer.refresh_from_db()
self.role.refresh_from_db()
self.assertIsNone(self.customer.cuser_role_id)
self.assertFalse(self.role.customer_panel)
self.assertEqual(self.post('token/refresh/', {'refresh': tokens['refresh']}).status_code, 401)
def test_duplicate_migration_audits_before_changing_any_data(self):
import importlib
from django.apps import apps
from django.db import connection
duplicate = Customer.objects.create(name_family='Legacy', mobile=989123456789, sys_ws=self.workspace)
migration = importlib.import_module('apps.customer.migrations.0008_customer_mobile_unique')
with self.assertRaises(RuntimeError):
migration.check_and_normalize_mobiles(apps, Mock(connection=connection))
duplicate.refresh_from_db()
self.assertEqual(duplicate.mobile, 989123456789)
def test_migration_normalizes_legacy_country_prefix(self):
import importlib
from django.apps import apps
from django.db import connection
legacy = Customer.objects.create(name_family='Legacy', mobile=989333333333, sys_ws=self.workspace)
migration = importlib.import_module('apps.customer.migrations.0008_customer_mobile_unique')
migration.check_and_normalize_mobiles(apps, Mock(connection=connection))
legacy.refresh_from_db()
self.assertEqual(legacy.mobile, 9333333333)
def test_customer_excel_import_reports_duplicate_and_rolls_back_all_rows(self):
import pandas as pd
self.city.ostan = City.objects.create(name='Province', is_ostan=True)
self.city.save()
role = CuserRole.objects.create(cuser=self.user, role_type=self.admin_type, sys_ws=self.workspace,
name_family='Admin', mobile=self.user.mobile, is_active=True, mobile_verify=True)
self.use_role(role)
rows = [{'name': 'New', 'mobile': 9333333333, 'phone': None, 'address': None,
'ostan': 'Province', 'shar': 'City', 'id_mantaghe': self.group.pk},
{'name': 'Duplicate', 'mobile': self.user.mobile, 'phone': None, 'address': None,
'ostan': 'Province', 'shar': 'City', 'id_mantaghe': self.group.pk}]
with patch('apps.shop.views.excel_import.pd.read_excel', return_value=pd.DataFrame(rows)):
response = self.client.get('/shop/import/customer/')
self.assertEqual(response.status_code, 400)
self.assertIn('mobile', response.data['errors'])
self.assertEqual(Customer.objects.count(), 1)
def test_anonymous_import_requests_are_rejected(self):
self.assertEqual(self.client.get('/shop/import/customer/').status_code, 401)
self.assertEqual(self.client.get('/shop/import/').status_code, 401)
def test_recorded_session_expiry_is_enforced(self):
tokens = self.use_role()
JwtToken.objects.update(expire=timezone.now()-timedelta(seconds=1))
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
self.assertEqual(self.post('token/refresh/', {'refresh': tokens['refresh']}).status_code, 401)
def test_inactive_role_and_workspace_reject_access(self):
self.use_role()
self.role.is_active = False
self.role.save()
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
self.role.is_active = True
self.role.save()
self.workspace.is_active = False
self.workspace.save()
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
def test_admin_user_creation_hashes_password_and_normalizes_mobile(self):
self.user.is_staff = True
self.user.is_superuser = True
self.user.save()
self.client.force_login(self.user)
response = self.client.post('/admin/cuser/cuser/add/', {
'username': 'new-user', 'name_family': 'New User', 'mobile': '+98 933-333-3333',
'password1': 'Valid-Password-9320!', 'password2': 'Valid-Password-9320!',
'is_active': 'on', 'is_staff': 'on', '_save': 'Save',
})
self.assertEqual(response.status_code, 302)
user = CUser.objects.get(username='new-user')
self.assertEqual(user.mobile, 9333333333)
self.assertTrue(user.check_password('Valid-Password-9320!'))
self.assertNotEqual(user.password, 'Valid-Password-9320!')
def test_admin_duplicate_user_mobile_is_a_form_error(self):
self.user.is_staff = True
self.user.is_superuser = True
self.user.save()
self.client.force_login(self.user)
response = self.client.post('/admin/cuser/cuser/add/', {
'username': 'duplicate', 'mobile': '+98 912-345-6789',
'password1': 'Valid-Password-9320!', 'password2': 'Valid-Password-9320!', '_save': 'Save',
})
self.assertEqual(response.status_code, 200)
self.assertIn('mobile', response.context['adminform'].form.errors)
self.assertEqual(CUser.objects.count(), 1)
class OrderingRegressionTests(RegressionFixtures):
def setUp(self):
self.first = ProductCategory.objects.create(name='First', sys_ws=self.workspace, order_cat=1, excel_order=20)
self.second = ProductCategory.objects.create(name='Second', sys_ws=self.workspace, order_cat=2, excel_order=10)
self.unordered = ProductCategory.objects.create(name='Unordered', sys_ws=self.workspace)
self.a = Product.objects.create(name='A', category=self.first, sys_ws=self.workspace, price=10, order_prd=2, excel_order=1, attr={'acc_vahed': 'Unit'})
self.b = Product.objects.create(name='B', category=self.first, sys_ws=self.workspace, price=20, order_prd=1, excel_order=2)
self.c = Product.objects.create(name='C', category=self.second, sys_ws=self.workspace, price=30, order_prd=1)
def test_display_order_groups_categories_and_products(self):
self.assertEqual(list(ProductCategory.objects.values_list('name', flat=True)), ['First', 'Second', 'Unordered'])
self.assertEqual(list(Product.objects.values_list('name', flat=True)), ['B', 'A', 'C'])
def test_admin_excel_independent_order_and_current_column_order(self):
model_admin = ProductAdmin(Product, admin.site)
sheet = load_workbook(BytesIO(model_admin.export_excel_order(None, Product.objects.all()).content)).active
self.assertEqual([row[2] for row in list(sheet.values)[1:]], ['C', 'A', 'B'])
sheet = load_workbook(BytesIO(model_admin.export_display_order(None, Product.objects.order_by('-name')).content)).active
self.assertEqual([row[2] for row in list(sheet.values)[1:]], ['C', 'B', 'A'])
category_admin = ProductCategoryAdmin(ProductCategory, admin.site)
sheet = load_workbook(BytesIO(category_admin.export_excel_order(None, ProductCategory.objects.all()).content)).active
self.assertEqual([row[1] for row in list(sheet.values)[1:]], ['Second', 'First', 'Unordered'])
def test_empty_excel_order_falls_back_to_display(self):
ProductCategory.objects.update(excel_order=None)
Product.objects.update(excel_order=None)
response = ProductAdmin(Product, admin.site).export_excel_order(None, Product.objects.all())
sheet = load_workbook(BytesIO(response.content)).active
self.assertEqual([row[2] for row in list(sheet.values)[1:]], ['B', 'A', 'C'])
def test_excel_keeps_formula_like_names_as_text(self):
response = excel_response(('Name',), [('=1+1',)], 'test.xlsx')
cell = load_workbook(BytesIO(response.content)).active['A2']
self.assertEqual(cell.data_type, 's')
self.assertEqual(cell.value, '=1+1')
def test_category_api_is_ordered_and_does_not_leak_other_workspace(self):
foreign = ProductCategory.objects.create(name='Foreign', sys_ws=self.other_workspace, order_cat=0)
Product.objects.create(name='Foreign product', sys_ws=self.other_workspace, category=foreign, price=1)
self.use_role()
response = self.client.get('/shop/v2/product-category/list/')
self.assertEqual(response.status_code, 200)
self.assertEqual([row['name'] for row in response.data], ['First', 'Second'])
response = self.client.get(f'/shop/product/list/{self.first.pk}/')
self.assertEqual(response.status_code, 200)
self.assertIsNone(response.data['category_img'])
self.assertEqual([row['name'] for row in response.data['products']], ['B', 'A'])
def test_order_excel_matches_independent_order_and_handles_empty_order(self):
order = Order.objects.create(customer=self.customer, total_price=60, factor_type=1, sys_ws=self.workspace)
for product in [self.b, self.a, self.c]:
OrderItem.objects.create(order=order, product=product, product_name=product.name, price=product.price, amount=1)
self.use_role(self.operator)
response = self.client.get(f'/shop/order/{order.pk}/operator/excel/')
self.assertEqual(response.status_code, 200)
sheet = load_workbook(BytesIO(response.content)).active
self.assertEqual([row[0] for row in list(sheet.values)[1:]], ['SecondC', 'FirstA', 'FirstB'])
OrderItem.objects.all().delete()
response = self.client.get(f'/shop/order/{order.pk}/operator/excel/')
self.assertEqual(response.status_code, 200)
self.assertEqual(load_workbook(BytesIO(response.content)).active.max_row, 1)
def test_order_excel_requires_operator_and_matching_workspace(self):
order = Order.objects.create(customer=self.customer, total_price=0, factor_type=1, sys_ws=self.other_workspace)
self.use_role()
self.assertEqual(self.client.get(f'/shop/order/{order.pk}/operator/excel/').status_code, 403)
self.use_role(self.operator)
self.assertEqual(self.client.get(f'/shop/order/{order.pk}/operator/excel/').status_code, 400)
def test_admin_bulk_edit_category_and_product_orders(self):
self.user.is_staff = True
self.user.is_superuser = True
self.user.save()
self.client.force_login(self.user)
for model, field, objects in [('productcategory', 'order_cat', [self.first, self.second, self.unordered]),
('product', 'order_prd', [self.a, self.b, self.c])]:
path = f'/admin/shop/{model}/'
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
data = {'_save': 'Save', 'form-TOTAL_FORMS': len(objects), 'form-INITIAL_FORMS': len(objects),
'form-MIN_NUM_FORMS': 0, 'form-MAX_NUM_FORMS': 1000}
for index, obj in enumerate(objects):
data.update({f'form-{index}-id': obj.pk, f'form-{index}-{field}': 10+index,
f'form-{index}-excel_order': 20-index})
self.assertEqual(self.client.post(path, data).status_code, 302)
for index, obj in enumerate(objects):
obj.refresh_from_db()
self.assertEqual(getattr(obj, field), 10+index)
self.assertEqual(obj.excel_order, 20-index)
+4 -2
View File
@@ -4,13 +4,15 @@ from apps.jwt_auth.views import (
CustomTokenObtainPairView,
LoginByUserView,
LoginByMobileView,
VerifyMobileAndGetTokenView
VerifyMobileAndGetTokenView,
LogoutView,
)
urlpatterns = [
path('logout/', LogoutView.as_view(), name='logout'),
path('login/username/', LoginByUserView.as_view(), name='login_by_username'),
path('login/mobile/', LoginByMobileView.as_view(), name='login_by_mobile'),
path('login/mobile/verify/', VerifyMobileAndGetTokenView.as_view(), name='verify_mobile'),
path('token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', CustomTokenRefreshView.as_view(), name='token_refresh'),
]
]
+107 -255
View File
@@ -1,297 +1,149 @@
from django.shortcuts import render
from django.utils import timezone
from rest_framework.views import APIView
from rest_framework import serializers
from rest_framework.response import Response
from django.db import transaction
from apps.jwt_auth.models import JwtToken as JwtTokenModel, CUserRoleLoginOpt
from apps.cuser.models import CuserRole as CuserRoleModel, RoleType as RoleTypeModel
from apps.customer.models import Customer as CustomerModel, CustomerResponsible as CustomerResponsibleModel
from rest_framework_simplejwt.tokens import RefreshToken, AccessToken, Token
from rest_framework_simplejwt.authentication import JWTAuthentication
from apps.workspace.models import Workspace as WorkspaceModel
from django.utils import timezone
from rest_framework import serializers, status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from rest_framework_simplejwt.tokens import RefreshToken
from apps.cuser.models import CUser, CuserRole, RoleType
from apps.jwt_auth.models import JwtToken, CUserRoleLoginOpt
from apps.jwt_auth.services import login_by_username, login_by_mobile, verify_mobile_and_get_token
from django.contrib.auth import get_user_model
from rest_framework import status
CUser = get_user_model()
# Create your views here.
from apps.jwt_auth.session import issue_tokens, validate_session_claims
from apps.workspace.models import Workspace
class VerifyMobileAndGetTokenView(APIView):
class InputSerializer(serializers.Serializer):
mobile = serializers.IntegerField(required=True)
opt = serializers.IntegerField(required=True)
class RoleTypeSerializer(serializers.ModelSerializer):
class Meta:
model = RoleType
fields = ('role_name', 'role_name_en')
class OutputSerializer(serializers.ModelSerializer):
class CUserRoleSerializer(serializers.ModelSerializer):
class RoleTypeSerializer(serializers.ModelSerializer):
class Meta:
model = RoleTypeModel
fields = ('role_name', )
role_type = RoleTypeSerializer()
class Meta:
model = CuserRoleModel
fields = ('name_family', 'title', 'role_type')
cuser_role = CUserRoleSerializer()
class RoleSerializer(serializers.ModelSerializer):
class WorkspaceSerializer(serializers.ModelSerializer):
class Meta:
model = CUserRoleLoginOpt
fields = ('cuser_role', 'expire', 'uuid')
model = Workspace
fields = ('id', 'name')
def post(self, request):
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
role_type = RoleTypeSerializer()
sys_ws = WorkspaceSerializer()
login_opts = verify_mobile_and_get_token(**serializer.validated_data)
response_data = self.OutputSerializer(login_opts, many=True).data
return Response(response_data)
class Meta:
model = CuserRole
fields = ('name_family', 'title', 'role_type', 'sys_ws')
class LoginByMobileView(APIView):
class LoginOptionSerializer(serializers.ModelSerializer):
cuser_role = RoleSerializer()
class Meta:
model = CUserRoleLoginOpt
fields = ('cuser_role', 'expire', 'uuid')
class PublicAuthView(APIView):
authentication_classes = ()
permission_classes = (AllowAny,)
def get_authenticate_header(self, request):
return 'Bearer realm="api"'
class VerifyMobileAndGetTokenView(PublicAuthView):
class InputSerializer(serializers.Serializer):
mobile = serializers.IntegerField(required=True)
mobile = serializers.CharField(required=True)
opt = serializers.IntegerField(required=True, min_value=100000, max_value=999999)
def post(self, request):
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
login_by_mobile(**serializer.validated_data)
output = {
"result": {
"mobile": serializer.validated_data.get('mobile'),
"message": "opt code send"
}
}
return Response(output)
options = verify_mobile_and_get_token(**serializer.validated_data)
return Response(LoginOptionSerializer(options, many=True).data)
class LoginByUserView(APIView):
class LoginByMobileView(PublicAuthView):
class InputSerializer(serializers.Serializer):
mobile = serializers.CharField(required=True)
def post(self, request):
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
mobile = login_by_mobile(**serializer.validated_data)
return Response({'result': {'mobile': mobile, 'message': 'opt code send'}})
class LoginByUserView(PublicAuthView):
class InputSerializer(serializers.Serializer):
username = serializers.CharField(required=True)
password = serializers.CharField(required=True)
class OutputSerializer(serializers.ModelSerializer):
class CUserRoleSerializer(serializers.ModelSerializer):
class RoleTypeSerializer(serializers.ModelSerializer):
class Meta:
model = RoleTypeModel
fields = ('role_name', )
role_type = RoleTypeSerializer()
class Meta:
model = CuserRoleModel
fields = ('name_family', 'title', 'role_type')
cuser_role = CUserRoleSerializer()
class Meta:
model = CUserRoleLoginOpt
fields = ('cuser_role', 'expire', 'uuid')
password = serializers.CharField(required=True, trim_whitespace=False, write_only=True)
def post(self, request):
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
username = serializer.validated_data.get('username')
password = serializer.validated_data.get('password')
login_opts = login_by_username(username=username, password=password)
response_data = self.OutputSerializer(login_opts, many=True).data
return Response(response_data)
options = login_by_username(**serializer.validated_data)
return Response(LoginOptionSerializer(options, many=True).data)
class CustomTokenObtainPairView(APIView):
class CustomTokenObtainPairView(PublicAuthView):
class InputSerializer(serializers.Serializer):
uuid_opt = serializers.CharField(required=True)
class OutputSerializer(serializers.Serializer):
access = serializers.CharField(required=True)
refresh = serializers.CharField(required=True)
uuid_opt = serializers.UUIDField(required=True)
@transaction.atomic
def post(self, request):
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
uuid_opt = serializer.validated_data.get('uuid_opt')
try:
user_login_opt = CUserRoleLoginOpt.objects.get(uuid=uuid_opt)
user = user_login_opt.cuser
cuser_role = user_login_opt.cuser_role
user_id = CUserRoleLoginOpt.objects.values_list('cuser_id', flat=True).get(
uuid=serializer.validated_data['uuid_opt'], expire__gt=timezone.now())
CUser.objects.select_for_update().get(pk=user_id)
option = CUserRoleLoginOpt.objects.select_related(
'cuser_role__cuser', 'cuser_role__role_type', 'cuser_role__sys_ws',
).get(uuid=serializer.validated_data['uuid_opt'], expire__gt=timezone.now())
except (CUserRoleLoginOpt.DoesNotExist, CUser.DoesNotExist):
raise InvalidToken('اعتبار درخواست ورود تمام شده است. دوباره وارد شوید.')
if option.cuser_id != option.cuser_role.cuser_id:
raise InvalidToken('درخواست ورود معتبر نیست.')
data = issue_tokens(option.cuser_role)
CUserRoleLoginOpt.objects.filter(cuser_id=option.cuser_id).delete()
return Response(data)
workspace = cuser_role.sys_ws
if user_login_opt:
refresh = RefreshToken.for_user(user)
#if 'user_id' in refresh:
# del refresh['user_id']
#print(cuser_role.uuid)
'''try:
#customerObj = CustomerModel.objects.get(cuser_role__uuid=str(cuser_role.uuid))
# workspace = customerObj.sys_ws
workspace = cuser_role.sys_ws
except:
operator = CustomerResponsibleModel.objects.get(cuser_role=cuser_role)
workspace = operator.sys_ws'''
refresh['workspace_uuid'] = str(workspace.uuid)
refresh['cuser_role_uuid'] = str(cuser_role.uuid)
refresh['user_uuid'] = str(user.uuid)
#print(str(refresh))
try:
jwt_db = JwtTokenModel(refresh=str(refresh), access=str(refresh.access_token),
cuser=user, cuser_role=cuser_role)
jwt_db.save()
try:
CUserRoleLoginOpt.objects.filter(cuser=user).delete()
except Exception as exp:
pass
except Exception as exp:
return Response({'error': str(exp)})
new_token_data = {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
out_data = self.OutputSerializer(data=new_token_data)
out_data.is_valid(raise_exception=True)
return Response(out_data.data)
else:
return Response({"error": 'userpass is not True'})
except CUserRoleLoginOpt.DoesNotExist:
return Response({"error": 'Token not valid'})
except Exception as exp:
return Response({"error": 'user or pass is not true 2'})
class CustomTokenRefreshView(APIView):
class CustomTokenRefreshView(PublicAuthView):
class InputSerializer(serializers.Serializer):
refresh = serializers.CharField(required=True)
class OutputSerializer(serializers.Serializer):
access = serializers.CharField(required=True)
@transaction.atomic
def post(self, request):
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
refresh_value = serializer.validated_data['refresh']
try:
refresh = RefreshToken(refresh_value)
session = JwtToken.objects.select_for_update().select_related(
'cuser_role__cuser', 'cuser_role__role_type', 'cuser_role__sys_ws',
).get(refresh=refresh_value, is_blocked=False)
except (TokenError, JwtToken.DoesNotExist):
raise InvalidToken('نشست شما منقضی یا باطل شده است. دوباره وارد شوید.')
validate_session_claims(session, refresh)
return Response(issue_tokens(session.cuser_role, session=session))
class LogoutView(PublicAuthView):
class InputSerializer(serializers.Serializer):
refresh = serializers.CharField(required=True)
def post(self, request):
with transaction.atomic():
try:
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
refresh_token = serializer.validated_data.get('refresh')
print(refresh_token)
# check: token is valid?
ref_token = RefreshToken(token=refresh_token)
print("ref")
# first token must exists in whitelist and not blocked
token_exist = JwtTokenModel.objects.get(refresh=str(refresh_token))
print("exsist")
token_is_blocked = JwtTokenModel.objects.get(refresh=str(refresh_token)).is_blocked
print("yess")
if token_exist and token_is_blocked is False:
JwtTokenModel.objects.get(refresh=str(refresh_token)).delete()
else:
raise ValueError('refresh token is not in whitelist or blocked!')
user_uuid = ref_token.payload.get('user_uuid')
user_id = ref_token.payload.get('user_id')
cuser_role_uuid = ref_token.payload.get('cuser_role_uuid')
token_exp_timestamp = ref_token.payload.get('exp')
token_exp = timezone.make_aware(timezone.datetime.fromtimestamp(token_exp_timestamp), timezone.utc)
user = CUser.objects.get(id=user_id)
cuser_role = CuserRoleModel.objects.get(uuid=cuser_role_uuid)
print("OKH###")
if ref_token:
new_token = RefreshToken.for_user(user)
#if 'user_id' in new_token:
#del new_token['user_id']
new_token['user_uuid'] = str(user.uuid)
new_token['cuser_role_uuid'] = str(cuser_role.uuid)
print("OKOKKO 555")
#workspace = WorkspaceModel.objects.get(cuser_role_owner__uuid=str(cuser_role.uuid))
try:
customerObj = CustomerModel.objects.get(cuser_role__uuid=str(cuser_role.uuid))
new_token['workspace_uuid'] = str(customerObj.sys_ws.uuid)
except:
operator = CustomerResponsibleModel.objects.get(cuser_role__uuid=str(cuser_role.uuid))
new_token['workspace_uuid'] = str(operator.sys_ws.uuid)
new_access_token = new_token.access_token
new_token_data = {
'refresh': str(new_token),
'access': str(new_access_token),
}
print("OKOKKO")
new_token_obj = JwtTokenModel(refresh=str(new_token),
access=str(new_token_data.get('access')),
expire=token_exp,
cuser=user,
cuser_role=cuser_role
)
print("OKOKKO 222")
new_token_obj.save()
out_data = self.OutputSerializer(data=new_token_data)
out_data.is_valid(raise_exception=True)
return Response(out_data.data)
except Exception as e:
transaction.set_rollback(True)
return Response({"error": str(e)})
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
value = serializer.validated_data['refresh']
try:
token = RefreshToken(value)
except TokenError:
return Response(status=status.HTTP_204_NO_CONTENT)
# Possession of the signed refresh token permits logout even after access expires.
if type(token.get('session_id')) is int and type(token.get('user_id')) is int:
JwtToken.objects.filter(pk=token['session_id'], cuser_id=token['user_id']).update(is_blocked=True)
else:
JwtToken.objects.filter(refresh=value).update(is_blocked=True)
return Response(status=status.HTTP_204_NO_CONTENT)