diff --git a/apps/audits/hands.py b/apps/audits/hands.py new file mode 100644 index 000000000..ec51c5a2b --- /dev/null +++ b/apps/audits/hands.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# diff --git a/apps/audits/migrations/0004_operatelog_passwordchangelog_userloginlog.py b/apps/audits/migrations/0004_operatelog_passwordchangelog_userloginlog.py index c538432de..a6002abca 100644 --- a/apps/audits/migrations/0004_operatelog_passwordchangelog_userloginlog.py +++ b/apps/audits/migrations/0004_operatelog_passwordchangelog_userloginlog.py @@ -38,14 +38,4 @@ class Migration(migrations.Migration): ('datetime', models.DateTimeField(auto_now=True)), ], ), - migrations.CreateModel( - name='UserLoginLog', - fields=[ - ], - options={ - 'proxy': True, - 'indexes': [], - }, - bases=('users.loginlog',), - ), ] diff --git a/apps/audits/migrations/0005_auto_20190228_1715.py b/apps/audits/migrations/0005_auto_20190228_1715.py new file mode 100644 index 000000000..70fc0a2c2 --- /dev/null +++ b/apps/audits/migrations/0005_auto_20190228_1715.py @@ -0,0 +1,39 @@ +# Generated by Django 2.1.7 on 2019-02-28 09:15 + +from django.db import migrations, models, connection +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('audits', '0004_operatelog_passwordchangelog_userloginlog'), + ] + + operations = [ + migrations.CreateModel( + name='UserLoginLog', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('username', models.CharField(max_length=128, verbose_name='Username')), + ('type', models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')], max_length=2, verbose_name='Login type')), + ('ip', models.GenericIPAddressField(verbose_name='Login ip')), + ('city', models.CharField(blank=True, max_length=254, null=True, verbose_name='Login city')), + ('user_agent', models.CharField(blank=True, max_length=254, null=True, verbose_name='User agent')), + ('mfa', models.SmallIntegerField(choices=[(0, 'Disabled'), (1, 'Enabled'), (2, '-')], default=2, verbose_name='MFA')), + ('reason', models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist'), (4, 'Password expired')], default=0, verbose_name='Reason')), + ('status', models.BooleanField(choices=[(True, 'Success'), (False, 'Failed')], default=True, max_length=2, verbose_name='Status')), + ('datetime', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date login')), + ], + options={ + 'ordering': ['-datetime', 'username'], + }, + ), + ] + drop_table_sql = "DROP TABLE audits_userloginlog" + rename_table_sql = "RENAME TABLE users_loginlog TO audits_userloginlog" + table_exist = 'users_loginlog' in connection.introspection.table_names() + if table_exist: + operations.append(migrations.RunSQL(drop_table_sql)) + operations.append(migrations.RunSQL(rename_table_sql)) diff --git a/apps/audits/models.py b/apps/audits/models.py index bc4b6960a..7e2302bb4 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -2,9 +2,9 @@ import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.utils import timezone from orgs.mixins import OrgModelMixin -from .hands import LoginLog __all__ = [ 'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog', @@ -55,6 +55,50 @@ class PasswordChangeLog(models.Model): return "{} change {}'s password".format(self.change_by, self.user) -class UserLoginLog(LoginLog): +class UserLoginLog(models.Model): + LOGIN_TYPE_CHOICE = ( + ('W', 'Web'), + ('T', 'Terminal'), + ) + + MFA_DISABLED = 0 + MFA_ENABLED = 1 + MFA_UNKNOWN = 2 + + MFA_CHOICE = ( + (MFA_DISABLED, _('Disabled')), + (MFA_ENABLED, _('Enabled')), + (MFA_UNKNOWN, _('-')), + ) + + REASON_NOTHING = 0 + REASON_PASSWORD = 1 + REASON_MFA = 2 + REASON_NOT_EXIST = 3 + REASON_PASSWORD_EXPIRED = 4 + + REASON_CHOICE = ( + (REASON_NOTHING, _('-')), + (REASON_PASSWORD, _('Username/password check failed')), + (REASON_MFA, _('MFA authentication failed')), + (REASON_NOT_EXIST, _("Username does not exist")), + (REASON_PASSWORD_EXPIRED, _("Password expired")), + ) + + STATUS_CHOICE = ( + (True, _('Success')), + (False, _('Failed')) + ) + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + username = models.CharField(max_length=128, verbose_name=_('Username')) + type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type')) + ip = models.GenericIPAddressField(verbose_name=_('Login ip')) + city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city')) + user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent')) + mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA')) + reason = models.SmallIntegerField(default=0, choices=REASON_CHOICE, verbose_name=_('Reason')) + status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status')) + datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login')) + class Meta: - proxy = True + ordering = ['-datetime', 'username'] diff --git a/apps/audits/views.py b/apps/audits/views.py index b19514e9b..a862ca066 100644 --- a/apps/audits/views.py +++ b/apps/audits/views.py @@ -8,7 +8,6 @@ from common.permissions import AdminUserRequiredMixin from orgs.utils import current_org from ops.views import CommandExecutionListView as UserCommandExecutionListView -from users.models import User from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog diff --git a/apps/authentication/api/auth.py b/apps/authentication/api/auth.py index 3e2bc10b3..2d9cfe367 100644 --- a/apps/authentication/api/auth.py +++ b/apps/authentication/api/auth.py @@ -16,8 +16,9 @@ from common.utils import get_logger, get_request_ip from common.permissions import IsOrgAdminOrAppUser from orgs.mixins import RootOrgViewMixin from users.serializers import UserSerializer -from users.models import User, LoginLog +from users.models import User from assets.models import Asset, SystemUser +from audits.models import UserLoginLog as LoginLog from users.utils import ( check_user_valid, check_otp_code, increase_login_failed_count, is_block_login, clean_failed_count diff --git a/apps/authentication/backends/api.py b/apps/authentication/backends/api.py index eab168a07..75b9bdb3f 100644 --- a/apps/authentication/backends/api.py +++ b/apps/authentication/backends/api.py @@ -8,13 +8,13 @@ from django.core.cache import cache from django.conf import settings from django.utils.translation import ugettext as _ from django.utils.six import text_type -from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth import get_user_model from rest_framework import HTTP_HEADER_ENCODING from rest_framework import authentication, exceptions from rest_framework.authentication import CSRFCheck from common.utils import get_object_or_none, make_signature, http_to_unixtime -from users.models import User, AccessKey, PrivateToken +from ..models import AccessKey, PrivateToken def get_request_date_header(request): @@ -42,7 +42,6 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): 失败 """ keyword = 'Sign' - model = AccessKey def authenticate(self, request): auth = authentication.get_authorization_header(request).split() @@ -109,7 +108,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): class AccessTokenAuthentication(authentication.BaseAuthentication): keyword = 'Bearer' - model = User + model = get_user_model() expiration = settings.TOKEN_EXPIRATION or 3600 def authenticate(self, request): @@ -133,10 +132,9 @@ class AccessTokenAuthentication(authentication.BaseAuthentication): raise exceptions.AuthenticationFailed(msg) return self.authenticate_credentials(token) - @staticmethod - def authenticate_credentials(token): + def authenticate_credentials(self, token): user_id = cache.get(token) - user = get_object_or_none(User, id=user_id) + user = get_object_or_none(self.model, id=user_id) if not user: msg = _('Invalid token or cache refreshed.') diff --git a/apps/authentication/const.py b/apps/authentication/const.py new file mode 100644 index 000000000..f19a64d9a --- /dev/null +++ b/apps/authentication/const.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# + + diff --git a/apps/authentication/forms.py b/apps/authentication/forms.py new file mode 100644 index 000000000..c722629db --- /dev/null +++ b/apps/authentication/forms.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# + +from django import forms +from django.contrib.auth.forms import AuthenticationForm +from django.utils.translation import gettext_lazy as _ +from captcha.fields import CaptchaField + + +class UserLoginForm(AuthenticationForm): + username = forms.CharField(label=_('Username'), max_length=100) + password = forms.CharField( + label=_('Password'), widget=forms.PasswordInput, + max_length=128, strip=False + ) + + def confirm_login_allowed(self, user): + if not user.is_staff: + raise forms.ValidationError( + self.error_messages['inactive'], + code='inactive',) + + +class UserLoginCaptchaForm(UserLoginForm): + captcha = CaptchaField() + + +class UserCheckOtpCodeForm(forms.Form): + otp_code = forms.CharField(label=_('MFA code'), max_length=6) diff --git a/apps/authentication/migrations/0001_initial.py b/apps/authentication/migrations/0001_initial.py new file mode 100644 index 000000000..f1c5f4710 --- /dev/null +++ b/apps/authentication/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# Generated by Django 2.1.7 on 2019-02-28 08:07 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='AccessKey', + fields=[ + ('id', + models.UUIDField(default=uuid.uuid4, editable=False, + primary_key=True, serialize=False, + verbose_name='AccessKeyID')), + ('secret', + models.UUIDField(default=uuid.uuid4, editable=False, + verbose_name='AccessKeySecret')), + ('user', models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='access_keys', + to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + ), + migrations.CreateModel( + name='PrivateToken', + fields=[ + ('key', + models.CharField(max_length=40, primary_key=True, + serialize=False, verbose_name='Key')), + ('created', models.DateTimeField(auto_now_add=True, + verbose_name='Created')), + ('user', models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name='auth_token', + to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'Private Token', + }, + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index e69de29bb..e4e822408 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -0,0 +1,33 @@ +import uuid +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from rest_framework.authtoken.models import Token +from django.conf import settings + + +class AccessKey(models.Model): + id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, + default=uuid.uuid4, editable=False) + secret = models.UUIDField(verbose_name='AccessKeySecret', + default=uuid.uuid4, editable=False) + user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User', + on_delete=models.CASCADE, related_name='access_keys') + + def get_id(self): + return str(self.id) + + def get_secret(self): + return str(self.secret) + + def get_full_value(self): + return '{}:{}'.format(self.id, self.secret) + + def __str__(self): + return str(self.id) + + +class PrivateToken(Token): + """Inherit from auth token, otherwise migration is boring""" + + class Meta: + verbose_name = _('Private Token') \ No newline at end of file diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py new file mode 100644 index 000000000..cf4968a56 --- /dev/null +++ b/apps/authentication/serializers.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import serializers + +from .models import AccessKey + + +__all__ = ['AccessKeySerializer'] + + +class AccessKeySerializer(serializers.ModelSerializer): + + class Meta: + model = AccessKey + fields = ['id', 'secret'] + read_only_fields = ['id', 'secret'] diff --git a/apps/authentication/utils.py b/apps/authentication/utils.py index 237730a21..baf2fff31 100644 --- a/apps/authentication/utils.py +++ b/apps/authentication/utils.py @@ -5,7 +5,7 @@ from common.utils import get_ip_city, validate_ip def write_login_log(*args, **kwargs): - from users.models import LoginLog + from audits.models import UserLoginLog default_city = _("Unknown") ip = kwargs.get('ip', '') if not (ip and validate_ip(ip)): @@ -14,5 +14,5 @@ def write_login_log(*args, **kwargs): else: city = get_ip_city(ip) or default_city kwargs.update({'ip': ip, 'city': city}) - LoginLog.objects.create(**kwargs) + UserLoginLog.objects.create(**kwargs) diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index 815c010ce..89d647291 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -17,14 +17,15 @@ from django.views.generic.edit import FormView from django.conf import settings from common.utils import get_request_ip -from authentication.signals import post_auth_success, post_auth_failed -from users import forms -from users.models import User, LoginLog +from users.models import User +from audits.models import UserLoginLog as LoginLog from users.utils import ( check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user, set_tmp_user_to_cache, increase_login_failed_count, redirect_user_first_login_or_index, ) +from ..signals import post_auth_success, post_auth_failed +from .. import forms __all__ = [ diff --git a/apps/orgs/signals_handler.py b/apps/orgs/signals_handler.py index a60ab3b7d..64c222136 100644 --- a/apps/orgs/signals_handler.py +++ b/apps/orgs/signals_handler.py @@ -23,7 +23,7 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs): set_current_org(old_org) if instance and not created: - instance.expire_cache() + instance.expire_user_cache() @receiver(m2m_changed, sender=Organization.users.through) diff --git a/apps/users/forms.py b/apps/users/forms.py index 521bc13ae..2ab6f11ac 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -1,9 +1,7 @@ # ~*~ coding: utf-8 ~*~ from django import forms -from django.contrib.auth.forms import AuthenticationForm from django.utils.translation import gettext_lazy as _ -from captcha.fields import CaptchaField from common.utils import validate_ssh_public_key from orgs.mixins import OrgModelForm @@ -11,24 +9,6 @@ from orgs.utils import current_org from .models import User, UserGroup -class UserLoginForm(AuthenticationForm): - username = forms.CharField(label=_('Username'), max_length=100) - password = forms.CharField( - label=_('Password'), widget=forms.PasswordInput, - max_length=128, strip=False - ) - - def confirm_login_allowed(self, user): - if not user.is_staff: - raise forms.ValidationError( - self.error_messages['inactive'], - code='inactive',) - - -class UserLoginCaptchaForm(UserLoginForm): - captcha = CaptchaField() - - class UserCheckPasswordForm(forms.Form): username = forms.CharField(label=_('Username'), max_length=100) password = forms.CharField( diff --git a/apps/users/migrations/0001_initial.py b/apps/users/migrations/0001_initial.py index 01edf24b6..7741912bf 100644 --- a/apps/users/migrations/0001_initial.py +++ b/apps/users/migrations/0001_initial.py @@ -4,10 +4,8 @@ from __future__ import unicode_literals import common.utils from django.contrib.auth.hashers import make_password -from django.conf import settings import django.contrib.auth.models from django.db import migrations, models -import django.db.models.deletion import django.utils.timezone import uuid @@ -75,40 +73,6 @@ class Migration(migrations.Migration): ('objects', django.contrib.auth.models.UserManager()), ], ), - migrations.CreateModel( - name='AccessKey', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='AccessKeyID')), - ('secret', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='AccessKeySecret')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_key', to=settings.AUTH_USER_MODEL, verbose_name='User')), - ], - ), - migrations.CreateModel( - name='LoginLog', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('username', models.CharField(max_length=20, verbose_name='Username')), - ('type', models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')], max_length=2, verbose_name='Login type')), - ('ip', models.GenericIPAddressField(verbose_name='Login ip')), - ('city', models.CharField(blank=True, max_length=254, null=True, verbose_name='Login city')), - ('user_agent', models.CharField(blank=True, max_length=254, null=True, verbose_name='User agent')), - ('datetime', models.DateTimeField(auto_now_add=True, verbose_name='Date login')), - ], - options={ - 'ordering': ['-datetime', 'username'], - }, - ), - migrations.CreateModel( - name='PrivateToken', - fields=[ - ('key', models.CharField(max_length=40, primary_key=True, serialize=False, verbose_name='Key')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='auth_token', to=settings.AUTH_USER_MODEL, verbose_name='User')), - ], - options={ - 'verbose_name': 'Private Token', - }, - ), migrations.CreateModel( name='UserGroup', fields=[ diff --git a/apps/users/migrations/0012_auto_20180710_1641.py b/apps/users/migrations/0012_auto_20180710_1641.py index ad6628941..282ef3bad 100644 --- a/apps/users/migrations/0012_auto_20180710_1641.py +++ b/apps/users/migrations/0012_auto_20180710_1641.py @@ -12,19 +12,4 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='loginlog', - name='mfa', - field=models.SmallIntegerField(choices=[(0, 'Disabled'), (1, 'Enabled'), (2, '-')], default=2, verbose_name='MFA'), - ), - migrations.AddField( - model_name='loginlog', - name='reason', - field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed')], default=0, verbose_name='Reason'), - ), - migrations.AddField( - model_name='loginlog', - name='status', - field=models.BooleanField(choices=[(True, 'Success'), (False, 'Failed')], default=True, max_length=2, verbose_name='Status'), - ), ] diff --git a/apps/users/migrations/0015_auto_20181105_1112.py b/apps/users/migrations/0015_auto_20181105_1112.py index b84e9fa97..dddafd8e8 100644 --- a/apps/users/migrations/0015_auto_20181105_1112.py +++ b/apps/users/migrations/0015_auto_20181105_1112.py @@ -10,14 +10,4 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( - model_name='loginlog', - name='reason', - field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist')], default=0, verbose_name='Reason'), - ), - migrations.AlterField( - model_name='loginlog', - name='username', - field=models.CharField(max_length=128, verbose_name='Username'), - ), ] diff --git a/apps/users/migrations/0017_auto_20181123_1113.py b/apps/users/migrations/0017_auto_20181123_1113.py index f9a4f7c97..1b3c9878e 100644 --- a/apps/users/migrations/0017_auto_20181123_1113.py +++ b/apps/users/migrations/0017_auto_20181123_1113.py @@ -1,8 +1,6 @@ # Generated by Django 2.1.1 on 2018-11-23 03:13 -from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): @@ -17,14 +15,4 @@ class Migration(migrations.Migration): name='date_password_last_updated', field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date password last updated'), ), - migrations.AlterField( - model_name='accesskey', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_keys', to=settings.AUTH_USER_MODEL, verbose_name='User'), - ), - migrations.AlterField( - model_name='loginlog', - name='reason', - field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist'), (4, 'Password expired')], default=0, verbose_name='Reason'), - ), ] diff --git a/apps/users/models/__init__.py b/apps/users/models/__init__.py index f3c9d1941..80080c292 100644 --- a/apps/users/models/__init__.py +++ b/apps/users/models/__init__.py @@ -4,5 +4,4 @@ from .user import * from .group import * -from .authentication import * from .utils import * diff --git a/apps/users/models/authentication.py b/apps/users/models/authentication.py deleted file mode 100644 index 6090dfc86..000000000 --- a/apps/users/models/authentication.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# - -import uuid -from django.db import models -from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ -from rest_framework.authtoken.models import Token -from .user import User - -__all__ = ['AccessKey', 'PrivateToken', 'LoginLog'] - - -class AccessKey(models.Model): - id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, - default=uuid.uuid4, editable=False) - secret = models.UUIDField(verbose_name='AccessKeySecret', - default=uuid.uuid4, editable=False) - user = models.ForeignKey(User, verbose_name='User', - on_delete=models.CASCADE, related_name='access_keys') - - def get_id(self): - return str(self.id) - - def get_secret(self): - return str(self.secret) - - def get_full_value(self): - return '{}:{}'.format(self.id, self.secret) - - def __str__(self): - return str(self.id) - - -class PrivateToken(Token): - """Inherit from auth token, otherwise migration is boring""" - - class Meta: - verbose_name = _('Private Token') - - -class LoginLog(models.Model): - LOGIN_TYPE_CHOICE = ( - ('W', 'Web'), - ('T', 'Terminal'), - ) - - MFA_DISABLED = 0 - MFA_ENABLED = 1 - MFA_UNKNOWN = 2 - - MFA_CHOICE = ( - (MFA_DISABLED, _('Disabled')), - (MFA_ENABLED, _('Enabled')), - (MFA_UNKNOWN, _('-')), - ) - - REASON_NOTHING = 0 - REASON_PASSWORD = 1 - REASON_MFA = 2 - REASON_NOT_EXIST = 3 - REASON_PASSWORD_EXPIRED = 4 - - REASON_CHOICE = ( - (REASON_NOTHING, _('-')), - (REASON_PASSWORD, _('Username/password check failed')), - (REASON_MFA, _('MFA authentication failed')), - (REASON_NOT_EXIST, _("Username does not exist")), - (REASON_PASSWORD_EXPIRED, _("Password expired")), - ) - - STATUS_CHOICE = ( - (True, _('Success')), - (False, _('Failed')) - ) - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - username = models.CharField(max_length=128, verbose_name=_('Username')) - type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type')) - ip = models.GenericIPAddressField(verbose_name=_('Login ip')) - city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city')) - user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent')) - mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA')) - reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason')) - status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status')) - datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login')) - - class Meta: - ordering = ['-datetime', 'username'] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 4bc196c7b..d86e42884 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -16,7 +16,6 @@ from django.utils import timezone from django.shortcuts import reverse from common.utils import get_signer, date_expired_default -from orgs.utils import current_org __all__ = ['User'] @@ -104,6 +103,8 @@ class User(AbstractUser): verbose_name=_('Date password last updated') ) + user_cache_key_prefix = '_User_{}' + def __str__(self): return '{0.name}({0.username})'.format(self) @@ -281,6 +282,7 @@ class User(AbstractUser): self.role = 'Admin' self.is_active = True super().save(*args, **kwargs) + self.expire_user_cache() @property def private_token(self): @@ -422,8 +424,26 @@ class User(AbstractUser): def delete(self, using=None, keep_parents=False): if self.pk == 1 or self.username == 'admin': return + self.expire_user_cache() return super(User, self).delete() + def expire_user_cache(self): + key = self.user_cache_key_prefix.format(self.id) + cache.delete(key) + + @classmethod + def get_user_or_from_cache(cls, uid): + key = cls.user_cache_key_prefix.format(uid) + user = cache.get(key) + if user: + return user + try: + user = cls.objects.get(id=uid) + cache.set(key, user, 3600) + except cls.DoesNotExist: + user = None + return user + class Meta: ordering = ['username'] verbose_name = _("User") diff --git a/apps/users/serializers/v2.py b/apps/users/serializers/v2.py index f936dc7bc..79914ef0e 100644 --- a/apps/users/serializers/v2.py +++ b/apps/users/serializers/v2.py @@ -2,15 +2,9 @@ # from django.utils.translation import ugettext as _ from rest_framework import serializers -from ..models import User, AccessKey +from ..models import User - -class AccessKeySerializer(serializers.ModelSerializer): - - class Meta: - model = AccessKey - fields = ['id', 'secret'] - read_only_fields = ['id', 'secret'] +from authentication.serializers import AccessKeySerializer class ServiceAccountSerializer(serializers.ModelSerializer):