perf: 修改usersession 模块位置

pull/11580/head
feng 2023-09-15 17:15:15 +08:00 committed by Bryan
parent bb1e674367
commit 1c2a362beb
18 changed files with 165 additions and 136 deletions

View File

@ -6,12 +6,16 @@ from importlib import import_module
from django.conf import settings from django.conf import settings
from django.db.models import F, Value, CharField, Q from django.db.models import F, Value, CharField, Q
from django.http import HttpResponse, FileResponse from django.http import HttpResponse, FileResponse
from django.utils import timezone
from django.utils.encoding import escape_uri_path from django.utils.encoding import escape_uri_path
from rest_framework import generics from rest_framework import generics
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from common.api import CommonApiMixin
from common.const.http import GET, POST from common.const.http import GET, POST
from common.drf.filters import DatetimeRangeFilterBackend from common.drf.filters import DatetimeRangeFilterBackend
from common.permissions import IsServiceAccount from common.permissions import IsServiceAccount
@ -28,13 +32,13 @@ from .backends import TYPE_ENGINE_MAPPING
from .const import ActivityChoices from .const import ActivityChoices
from .models import ( from .models import (
FTPLog, UserLoginLog, OperateLog, PasswordChangeLog, FTPLog, UserLoginLog, OperateLog, PasswordChangeLog,
ActivityLog, JobLog, ActivityLog, JobLog, UserSession
) )
from .serializers import ( from .serializers import (
FTPLogSerializer, UserLoginLogSerializer, JobLogSerializer, FTPLogSerializer, UserLoginLogSerializer, JobLogSerializer,
OperateLogSerializer, OperateLogActionDetailSerializer, OperateLogSerializer, OperateLogActionDetailSerializer,
PasswordChangeLogSerializer, ActivityUnionLogSerializer, PasswordChangeLogSerializer, ActivityUnionLogSerializer,
FileSerializer FileSerializer, UserSessionSerializer
) )
logger = get_logger(__name__) logger = get_logger(__name__)
@ -246,3 +250,41 @@ class PasswordChangeLogViewSet(OrgReadonlyModelViewSet):
user__in=[str(user) for user in users] user__in=[str(user) for user in users]
) )
return queryset return queryset
class UserSessionViewSet(CommonApiMixin, viewsets.ModelViewSet):
http_method_names = ('get', 'post', 'head', 'options', 'trace')
serializer_class = UserSessionSerializer
filterset_fields = ['id', 'ip', 'city', 'type']
search_fields = ['id', 'ip', 'city']
rbac_perms = {
'offline': ['users.offline_usersession']
}
@property
def org_user_ids(self):
user_ids = current_org.get_members().values_list('id', flat=True)
return user_ids
def get_queryset(self):
queryset = UserSession.objects.filter(date_expired__gt=timezone.now())
if current_org.is_root():
return queryset
user_ids = self.org_user_ids
queryset = queryset.filter(user_id__in=user_ids)
return queryset
@action(['POST'], detail=False, url_path='offline')
def offline(self, request, *args, **kwargs):
ids = request.data.get('ids', [])
queryset = self.get_queryset().exclude(key=request.session.session_key).filter(id__in=ids)
if not queryset.exists():
return Response(status=status.HTTP_200_OK)
keys = queryset.values_list('key', flat=True)
session_store_cls = import_module(settings.SESSION_ENGINE).SessionStore
for key in keys:
session_store_cls(key).delete()
queryset.delete()
return Response(status=status.HTTP_200_OK)

View File

@ -0,0 +1,37 @@
# Generated by Django 4.1.10 on 2023-09-15 08:58
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('audits', '0023_auto_20230906_1322'),
]
operations = [
migrations.CreateModel(
name='UserSession',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('ip', models.GenericIPAddressField(verbose_name='Login IP')),
('key', models.CharField(max_length=128, verbose_name='Session key')),
('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')),
('type', models.CharField(choices=[('W', 'Web'), ('T', 'Terminal'), ('U', 'Unknown')], max_length=2, verbose_name='Login type')),
('backend', models.CharField(default='', max_length=32, verbose_name='Authentication backend')),
('date_created', models.DateTimeField(blank=True, null=True, verbose_name='Date created')),
('date_expired', models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Date expired')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sessions', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'User session',
'ordering': ['-date_created'],
'permissions': [('offline_usersession', 'Offline ussr session')],
},
),
]

View File

@ -28,7 +28,8 @@ __all__ = [
"ActivityLog", "ActivityLog",
"PasswordChangeLog", "PasswordChangeLog",
"UserLoginLog", "UserLoginLog",
"JobLog" "JobLog",
"UserSession"
] ]
@ -245,3 +246,36 @@ class UserLoginLog(models.Model):
class Meta: class Meta:
ordering = ["-datetime", "username"] ordering = ["-datetime", "username"]
verbose_name = _("User login log") verbose_name = _("User login log")
class UserSession(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.GenericIPAddressField(verbose_name=_("Login IP"))
key = models.CharField(max_length=128, verbose_name=_("Session key"))
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"))
type = models.CharField(choices=LoginTypeChoices.choices, max_length=2, verbose_name=_("Login type"))
backend = models.CharField(max_length=32, default="", verbose_name=_("Authentication backend"))
date_created = models.DateTimeField(null=True, blank=True, verbose_name=_('Date created'))
date_expired = models.DateTimeField(null=True, blank=True, verbose_name=_("Date expired"), db_index=True)
user = models.ForeignKey(
'users.User', verbose_name=_('User'), related_name='sessions', on_delete=models.CASCADE
)
def __str__(self):
return '%s(%s)' % (self.user, self.ip)
@property
def backend_display(self):
return gettext(self.backend)
@classmethod
def clear_expired_sessions(cls):
cls.objects.filter(date_expired__lt=timezone.now()).delete()
class Meta:
ordering = ['-date_created']
verbose_name = _('User session')
permissions = [
('offline_usersession', _('Offline ussr session')),
]

View File

@ -4,12 +4,13 @@ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from audits.backends.db import OperateLogStore from audits.backends.db import OperateLogStore
from common.serializers.fields import LabeledChoiceField from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
from common.utils import reverse, i18n_trans from common.utils import reverse, i18n_trans
from common.utils.timezone import as_current_tz from common.utils.timezone import as_current_tz
from ops.serializers.job import JobExecutionSerializer from ops.serializers.job import JobExecutionSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from terminal.models import Session from terminal.models import Session
from users.models import User
from . import models from . import models
from .const import ( from .const import (
ActionChoices, OperateChoices, ActionChoices, OperateChoices,
@ -163,3 +164,27 @@ class ActivityUnionLogSerializer(serializers.Serializer):
class FileSerializer(serializers.Serializer): class FileSerializer(serializers.Serializer):
file = serializers.FileField(allow_empty_file=True) file = serializers.FileField(allow_empty_file=True)
class UserSessionSerializer(serializers.ModelSerializer):
type = LabeledChoiceField(choices=LoginTypeChoices.choices, label=_("Type"))
user = ObjectRelatedField(required=False, queryset=User.objects, label=_('User'))
is_current_user_session = serializers.SerializerMethodField()
class Meta:
model = models.UserSession
fields_mini = ['id']
fields_small = fields_mini + [
'type', 'ip', 'city', 'user_agent', 'user', 'is_current_user_session',
'backend', 'backend_display', 'date_created', 'date_expired'
]
fields = fields_small
extra_kwargs = {
"backend_display": {"label": _("Authentication backend")},
}
def get_is_current_user_session(self, obj):
request = self.context.get('request')
if not request:
return False
return request.session.session_key == obj.key

View File

@ -16,8 +16,9 @@ from audits.models import UserLoginLog
from authentication.signals import post_auth_failed, post_auth_success from authentication.signals import post_auth_failed, post_auth_success
from authentication.utils import check_different_city_login_if_need from authentication.utils import check_different_city_login_if_need
from common.utils import get_request_ip, get_logger from common.utils import get_request_ip, get_logger
from users.models import User, UserSession from users.models import User
from ..const import LoginTypeChoices from ..const import LoginTypeChoices
from ..models import UserSession
from ..utils import write_login_log from ..utils import write_login_log
logger = get_logger(__name__) logger = get_logger(__name__)

View File

@ -15,6 +15,7 @@ router.register(r'operate-logs', api.OperateLogViewSet, 'operate-log')
router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log') router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log')
router.register(r'job-logs', api.JobAuditViewSet, 'job-log') router.register(r'job-logs', api.JobAuditViewSet, 'job-log')
router.register(r'my-login-logs', api.MyLoginLogViewSet, 'my-login-log') router.register(r'my-login-logs', api.MyLoginLogViewSet, 'my-login-log')
router.register(r'user-sessions', api.UserSessionViewSet, 'user-session')
urlpatterns = [ urlpatterns = [
path('activities/', api.ResourceActivityAPIView.as_view(), name='resource-activities'), path('activities/', api.ResourceActivityAPIView.as_view(), name='resource-activities'),

View File

@ -36,7 +36,6 @@ system_user_perms += (user_perms + _view_all_joined_org_perms)
_auditor_perms = ( _auditor_perms = (
('rbac', 'menupermission', 'view', 'audit'), ('rbac', 'menupermission', 'view', 'audit'),
('users', 'usersession', '*', '*'),
('audits', '*', '*', '*'), ('audits', '*', '*', '*'),
('audits', 'joblog', '*', '*'), ('audits', 'joblog', '*', '*'),
('terminal', 'commandstorage', 'view', 'commandstorage'), ('terminal', 'commandstorage', 'view', 'commandstorage'),

View File

@ -28,7 +28,6 @@ exclude_permissions = (
('authentication', 'superconnectiontoken', 'change,delete', 'superconnectiontoken'), ('authentication', 'superconnectiontoken', 'change,delete', 'superconnectiontoken'),
('authentication', 'temptoken', 'delete', 'temptoken'), ('authentication', 'temptoken', 'delete', 'temptoken'),
('users', 'userpasswordhistory', '*', '*'), ('users', 'userpasswordhistory', '*', '*'),
('users', 'usersession', 'add,delete,change', 'usersession'),
('assets', 'adminuser', '*', '*'), ('assets', 'adminuser', '*', '*'),
('assets', 'assetgroup', '*', '*'), ('assets', 'assetgroup', '*', '*'),
('assets', 'cluster', '*', '*'), ('assets', 'cluster', '*', '*'),
@ -93,6 +92,7 @@ exclude_permissions = (
('audits', 'activitylog', 'add,delete,change', 'activitylog'), ('audits', 'activitylog', 'add,delete,change', 'activitylog'),
('audits', 'passwordchangelog', 'add,change,delete', 'passwordchangelog'), ('audits', 'passwordchangelog', 'add,change,delete', 'passwordchangelog'),
('audits', 'userloginlog', 'add,change,delete,change', 'userloginlog'), ('audits', 'userloginlog', 'add,change,delete,change', 'userloginlog'),
('audits', 'usersession', 'add,delete,change', 'usersession'),
('audits', 'ftplog', 'delete', 'ftplog'), ('audits', 'ftplog', 'delete', 'ftplog'),
('tickets', 'ticketassignee', '*', 'ticketassignee'), ('tickets', 'ticketassignee', '*', 'ticketassignee'),
('tickets', 'ticketflow', 'add,delete', 'ticketflow'), ('tickets', 'ticketflow', 'add,delete', 'ticketflow'),

View File

@ -98,7 +98,6 @@ special_pid_mapper = {
'terminal.endpoint': 'terminal_node', 'terminal.endpoint': 'terminal_node',
'terminal.endpointrule': 'terminal_node', 'terminal.endpointrule': 'terminal_node',
'audits.ftplog': 'terminal', 'audits.ftplog': 'terminal',
'users.usersession': 'terminal',
'perms.view_myassets': 'my_assets', 'perms.view_myassets': 'my_assets',
'ops.celerytask': 'task_center', 'ops.celerytask': 'task_center',
'ops.view_celerytaskexecution': 'task_center', 'ops.view_celerytaskexecution': 'task_center',
@ -113,6 +112,7 @@ special_pid_mapper = {
"settings.view_setting": "view_setting", "settings.view_setting": "view_setting",
"rbac.view_console": "view_console", "rbac.view_console": "view_console",
"rbac.view_audit": "view_audit", "rbac.view_audit": "view_audit",
'audits.usersession': 'view_audit',
"rbac.view_workbench": "view_workbench", "rbac.view_workbench": "view_workbench",
"rbac.view_webterminal": "view_workbench", "rbac.view_webterminal": "view_workbench",
"rbac.view_filemanager": "view_workbench", "rbac.view_filemanager": "view_workbench",

View File

@ -6,5 +6,4 @@ from .preference import *
from .profile import * from .profile import *
from .relation import * from .relation import *
from .service import * from .service import *
from .session import *
from .user import * from .user import *

View File

@ -1,51 +0,0 @@
from importlib import import_module
from django.conf import settings
from django.utils import timezone
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from common.api import CommonApiMixin
from orgs.utils import current_org
from users import serializers
from ..models import UserSession
class UserSessionViewSet(CommonApiMixin, viewsets.ModelViewSet):
http_method_names = ('get', 'post', 'head', 'options', 'trace')
serializer_class = serializers.UserSessionSerializer
filterset_fields = ['id', 'ip', 'city', 'type']
search_fields = ['id', 'ip', 'city']
rbac_perms = {
'offline': ['users.offline_usersession']
}
@property
def org_user_ids(self):
user_ids = current_org.get_members().values_list('id', flat=True)
return user_ids
def get_queryset(self):
queryset = UserSession.objects.filter(date_expired__gt=timezone.now())
if current_org.is_root():
return queryset
user_ids = self.org_user_ids
queryset = queryset.filter(user_id__in=user_ids)
return queryset
@action(['POST'], detail=False, url_path='offline')
def offline(self, request, *args, **kwargs):
ids = request.data.get('ids', [])
queryset = self.get_queryset().exclude(key=request.session.session_key).filter(id__in=ids)
if not queryset.exists():
return Response(status=status.HTTP_200_OK)
keys = queryset.values_list('key', flat=True)
session_store_cls = import_module(settings.SESSION_ENGINE).SessionStore
for key in keys:
session_store_cls(key).delete()
queryset.delete()
return Response(status=status.HTTP_200_OK)

View File

@ -0,0 +1,16 @@
# Generated by Django 4.1.10 on 2023-09-15 08:58
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0044_usersession'),
]
operations = [
migrations.DeleteModel(
name='UserSession',
),
]

View File

@ -4,6 +4,5 @@
from .group import * from .group import *
from .preference import * from .preference import *
from .session import *
from .user import * from .user import *
from .utils import * from .utils import *

View File

@ -1,40 +0,0 @@
import uuid
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext, gettext_lazy as _
from audits.const import LoginTypeChoices
class UserSession(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.GenericIPAddressField(verbose_name=_("Login IP"))
key = models.CharField(max_length=128, verbose_name=_("Session key"))
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"))
type = models.CharField(choices=LoginTypeChoices.choices, max_length=2, verbose_name=_("Login type"))
backend = models.CharField(max_length=32, default="", verbose_name=_("Authentication backend"))
date_created = models.DateTimeField(null=True, blank=True, verbose_name=_('Date created'))
date_expired = models.DateTimeField(null=True, blank=True, verbose_name=_("Date expired"), db_index=True)
user = models.ForeignKey(
'users.User', verbose_name=_('User'), related_name='sessions', on_delete=models.CASCADE
)
def __str__(self):
return '%s(%s)' % (self.user, self.ip)
@property
def backend_display(self):
return gettext(self.backend)
@classmethod
def clear_expired_sessions(cls):
cls.objects.filter(date_expired__lt=timezone.now()).delete()
class Meta:
ordering = ['-date_created']
verbose_name = _('User session')
permissions = [
('offline_usersession', _('Offline ussr session')),
]

View File

@ -4,5 +4,4 @@ from .group import *
from .preference import * from .preference import *
from .profile import * from .profile import *
from .realtion import * from .realtion import *
from .session import *
from .user import * from .user import *

View File

@ -1,32 +0,0 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from audits.const import LoginTypeChoices
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
from users.models import User
from ..models import UserSession
class UserSessionSerializer(serializers.ModelSerializer):
type = LabeledChoiceField(choices=LoginTypeChoices.choices, label=_("Type"))
user = ObjectRelatedField(required=False, queryset=User.objects, label=_('User'))
is_current_user_session = serializers.SerializerMethodField()
class Meta:
model = UserSession
fields_mini = ['id']
fields_small = fields_mini + [
'type', 'ip', 'city', 'user_agent', 'user', 'is_current_user_session',
'backend', 'backend_display', 'date_created', 'date_expired'
]
fields = fields_small
extra_kwargs = {
"backend_display": {"label": _("Authentication backend")},
}
def get_is_current_user_session(self, obj):
request = self.context.get('request')
if not request:
return False
return request.session.session_key == obj.key

View File

@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
from django_auth_ldap.backend import populate_user from django_auth_ldap.backend import populate_user
from django_cas_ng.signals import cas_user_authenticated from django_cas_ng.signals import cas_user_authenticated
from audits.models import UserSession
from authentication.backends.oauth2.signals import oauth2_create_or_update_user from authentication.backends.oauth2.signals import oauth2_create_or_update_user
from authentication.backends.oidc.signals import openid_create_or_update_user from authentication.backends.oidc.signals import openid_create_or_update_user
from authentication.backends.saml2.signals import saml2_create_or_update_user from authentication.backends.saml2.signals import saml2_create_or_update_user
@ -17,7 +18,7 @@ from common.decorators import on_transaction_commit
from common.utils import get_logger from common.utils import get_logger
from jumpserver.utils import get_current_request from jumpserver.utils import get_current_request
from ops.celery.decorator import register_as_period_task from ops.celery.decorator import register_as_period_task
from .models import User, UserPasswordHistory, UserSession from .models import User, UserPasswordHistory
from .signals import post_user_create from .signals import post_user_create
logger = get_logger(__file__) logger = get_logger(__file__)

View File

@ -17,7 +17,6 @@ router.register(r'groups', api.UserGroupViewSet, 'user-group')
router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'users-groups-relation') router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'users-groups-relation')
router.register(r'service-account-registrations', api.ServiceAccountRegistrationViewSet, 'service-account-registration') router.register(r'service-account-registrations', api.ServiceAccountRegistrationViewSet, 'service-account-registration')
router.register(r'connection-token', auth_api.ConnectionTokenViewSet, 'connection-token') router.register(r'connection-token', auth_api.ConnectionTokenViewSet, 'connection-token')
router.register(r'user-sessions', api.UserSessionViewSet, 'user-session')
urlpatterns = [ urlpatterns = [