mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-07-16 16:01:35 +00:00
perf: optimize user operation logs (#13221)
This commit is contained in:
parent
cdfb11549e
commit
dfd133cf5a
@ -52,7 +52,7 @@ class OperateLogStore(object):
|
|||||||
resource_map = {
|
resource_map = {
|
||||||
'Asset permission': lambda k, v: ActionChoices.display(int(v)) if k == 'Actions' else v
|
'Asset permission': lambda k, v: ActionChoices.display(int(v)) if k == 'Actions' else v
|
||||||
}
|
}
|
||||||
return resource_map.get(resource_type, lambda k, v: v)
|
return resource_map.get(resource_type, lambda k, v: _(v))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert_diff_friendly(cls, op_log):
|
def convert_diff_friendly(cls, op_log):
|
||||||
|
@ -37,6 +37,9 @@ class ActionChoices(TextChoices):
|
|||||||
approve = 'approve', _('Approve')
|
approve = 'approve', _('Approve')
|
||||||
close = 'close', _('Close')
|
close = 'close', _('Close')
|
||||||
|
|
||||||
|
# Custom action
|
||||||
|
finished = 'finished', _('Finished')
|
||||||
|
|
||||||
|
|
||||||
class LoginTypeChoices(TextChoices):
|
class LoginTypeChoices(TextChoices):
|
||||||
web = "W", _("Web")
|
web = "W", _("Web")
|
||||||
|
@ -58,7 +58,7 @@ class OperatorLogHandler(metaclass=Singleton):
|
|||||||
return
|
return
|
||||||
|
|
||||||
key = '%s_%s' % (self.CACHE_KEY, instance_id)
|
key = '%s_%s' % (self.CACHE_KEY, instance_id)
|
||||||
cache.set(key, instance_dict, 3 * 60)
|
cache.set(key, instance_dict, 3)
|
||||||
|
|
||||||
def get_instance_dict_from_cache(self, instance_id):
|
def get_instance_dict_from_cache(self, instance_id):
|
||||||
if instance_id is None:
|
if instance_id is None:
|
||||||
|
@ -257,6 +257,8 @@ class UserLoginLog(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class UserSession(models.Model):
|
class UserSession(models.Model):
|
||||||
|
_OPERATE_LOG_ACTION = {'delete': ActionChoices.finished}
|
||||||
|
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
ip = models.GenericIPAddressField(verbose_name=_("Login IP"))
|
ip = models.GenericIPAddressField(verbose_name=_("Login IP"))
|
||||||
key = models.CharField(max_length=128, verbose_name=_("Session key"))
|
key = models.CharField(max_length=128, verbose_name=_("Session key"))
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db.models.signals import post_save, pre_save, m2m_changed, pre_delete
|
from django.db.models.signals import (
|
||||||
|
pre_delete, pre_save, m2m_changed, post_delete, post_save
|
||||||
|
)
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
|
||||||
@ -94,7 +96,7 @@ def signal_of_operate_log_whether_continue(
|
|||||||
return condition
|
return condition
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save)
|
@receiver([pre_save, pre_delete])
|
||||||
def on_object_pre_create_or_update(
|
def on_object_pre_create_or_update(
|
||||||
sender, instance=None, raw=False, using=None, update_fields=None, **kwargs
|
sender, instance=None, raw=False, using=None, update_fields=None, **kwargs
|
||||||
):
|
):
|
||||||
@ -103,6 +105,7 @@ def on_object_pre_create_or_update(
|
|||||||
)
|
)
|
||||||
if not ok:
|
if not ok:
|
||||||
return
|
return
|
||||||
|
|
||||||
with translation.override('en'):
|
with translation.override('en'):
|
||||||
# users.PrivateToken Model 没有 id 有 pk字段
|
# users.PrivateToken Model 没有 id 有 pk字段
|
||||||
instance_id = getattr(instance, 'id', getattr(instance, 'pk', None))
|
instance_id = getattr(instance, 'id', getattr(instance, 'pk', None))
|
||||||
@ -145,7 +148,7 @@ def on_object_created_or_update(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete)
|
@receiver(post_delete)
|
||||||
def on_object_delete(sender, instance=None, **kwargs):
|
def on_object_delete(sender, instance=None, **kwargs):
|
||||||
ok = signal_of_operate_log_whether_continue(sender, instance, False)
|
ok = signal_of_operate_log_whether_continue(sender, instance, False)
|
||||||
if not ok:
|
if not ok:
|
||||||
@ -153,9 +156,15 @@ def on_object_delete(sender, instance=None, **kwargs):
|
|||||||
|
|
||||||
with translation.override('en'):
|
with translation.override('en'):
|
||||||
resource_type = sender._meta.verbose_name
|
resource_type = sender._meta.verbose_name
|
||||||
|
action = getattr(sender, '_OPERATE_LOG_ACTION', {})
|
||||||
|
action = action.get('delete', ActionChoices.delete)
|
||||||
|
instance_id = getattr(instance, 'id', getattr(instance, 'pk', None))
|
||||||
|
log_id, before = get_instance_dict_from_cache(instance_id)
|
||||||
|
if not log_id:
|
||||||
|
log_id, before = None, model_to_dict(instance)
|
||||||
create_or_update_operate_log(
|
create_or_update_operate_log(
|
||||||
ActionChoices.delete, resource_type,
|
action, resource_type, log_id=log_id,
|
||||||
resource=instance, before=model_to_dict(instance)
|
resource=instance, before=before,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -166,7 +175,7 @@ def on_django_start_set_operate_log_monitor_models(sender, **kwargs):
|
|||||||
'django_celery_beat', 'contenttypes', 'sessions', 'auth',
|
'django_celery_beat', 'contenttypes', 'sessions', 'auth',
|
||||||
}
|
}
|
||||||
exclude_models = {
|
exclude_models = {
|
||||||
'UserPasswordHistory', 'ContentType',
|
'UserPasswordHistory', 'ContentType', 'Asset',
|
||||||
'MessageContent', 'SiteMessage',
|
'MessageContent', 'SiteMessage',
|
||||||
'PlatformAutomation', 'PlatformProtocol', 'Protocol',
|
'PlatformAutomation', 'PlatformProtocol', 'Protocol',
|
||||||
'HistoricalAccount', 'GatheredUser', 'ApprovalRule',
|
'HistoricalAccount', 'GatheredUser', 'ApprovalRule',
|
||||||
@ -180,11 +189,13 @@ def on_django_start_set_operate_log_monitor_models(sender, **kwargs):
|
|||||||
'ApplyCommandTicket', 'ApplyLoginAssetTicket',
|
'ApplyCommandTicket', 'ApplyLoginAssetTicket',
|
||||||
'FavoriteAsset',
|
'FavoriteAsset',
|
||||||
}
|
}
|
||||||
|
include_models = {'UserSession'}
|
||||||
for i, app in enumerate(apps.get_models(), 1):
|
for i, app in enumerate(apps.get_models(), 1):
|
||||||
app_name = app._meta.app_label
|
app_name = app._meta.app_label
|
||||||
model_name = app._meta.object_name
|
model_name = app._meta.object_name
|
||||||
if app_name in exclude_apps or \
|
if app_name in exclude_apps or \
|
||||||
model_name in exclude_models or \
|
model_name in exclude_models or \
|
||||||
model_name.endswith('Execution'):
|
model_name.endswith('Execution'):
|
||||||
continue
|
if model_name not in include_models:
|
||||||
|
continue
|
||||||
MODELS_NEED_RECORD.add(model_name)
|
MODELS_NEED_RECORD.add(model_name)
|
||||||
|
@ -49,9 +49,15 @@ def _get_instance_field_value(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
value = getattr(instance, f.name, None) or getattr(instance, f.attname, None)
|
value = getattr(instance, f.name, None) or getattr(instance, f.attname, None)
|
||||||
if not isinstance(value, bool) and not value:
|
if not isinstance(value, (bool, int)) and not value:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
choices = getattr(f, 'choices', []) or []
|
||||||
|
for c_value, c_label in choices:
|
||||||
|
if c_value == value:
|
||||||
|
value = c_label
|
||||||
|
break
|
||||||
|
|
||||||
if getattr(f, 'primary_key', False):
|
if getattr(f, 'primary_key', False):
|
||||||
f.verbose_name = 'id'
|
f.verbose_name = 'id'
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
|
@ -75,6 +75,7 @@ class AuthMixin:
|
|||||||
if self.can_update_ssh_key():
|
if self.can_update_ssh_key():
|
||||||
self.public_key = public_key
|
self.public_key = public_key
|
||||||
self.save()
|
self.save()
|
||||||
|
post_user_change_password.send(self.__class__, user=self)
|
||||||
|
|
||||||
def can_update_password(self):
|
def can_update_password(self):
|
||||||
return self.is_local
|
return self.is_local
|
||||||
|
@ -17,6 +17,7 @@ from orgs.utils import current_org
|
|||||||
from rbac.builtin import BuiltinRole
|
from rbac.builtin import BuiltinRole
|
||||||
from rbac.models import OrgRoleBinding, SystemRoleBinding, Role
|
from rbac.models import OrgRoleBinding, SystemRoleBinding, Role
|
||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
|
from users.signals import post_user_change_password
|
||||||
from ..const import PasswordStrategy
|
from ..const import PasswordStrategy
|
||||||
from ..models import User
|
from ..models import User
|
||||||
|
|
||||||
@ -268,6 +269,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, ResourceLa
|
|||||||
instance = self.save_and_set_custom_m2m_fields(
|
instance = self.save_and_set_custom_m2m_fields(
|
||||||
validated_data, save_handler, created=False
|
validated_data, save_handler, created=False
|
||||||
)
|
)
|
||||||
|
if validated_data.get('public_key'):
|
||||||
|
post_user_change_password.send(instance.__class__, user=instance)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
@ -275,6 +278,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, ResourceLa
|
|||||||
instance = self.save_and_set_custom_m2m_fields(
|
instance = self.save_and_set_custom_m2m_fields(
|
||||||
validated_data, save_handler, created=True
|
validated_data, save_handler, created=True
|
||||||
)
|
)
|
||||||
|
if validated_data.get('public_key'):
|
||||||
|
post_user_change_password.send(instance.__class__, user=instance)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
Loading…
Reference in New Issue
Block a user