mirror of https://github.com/jumpserver/jumpserver
perf: 修改usersession 模块位置
parent
bb1e674367
commit
1c2a362beb
|
@ -6,12 +6,16 @@ from importlib import import_module
|
|||
from django.conf import settings
|
||||
from django.db.models import F, Value, CharField, Q
|
||||
from django.http import HttpResponse, FileResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import escape_uri_path
|
||||
from rest_framework import generics
|
||||
from rest_framework import status
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.api import CommonApiMixin
|
||||
from common.const.http import GET, POST
|
||||
from common.drf.filters import DatetimeRangeFilterBackend
|
||||
from common.permissions import IsServiceAccount
|
||||
|
@ -28,13 +32,13 @@ from .backends import TYPE_ENGINE_MAPPING
|
|||
from .const import ActivityChoices
|
||||
from .models import (
|
||||
FTPLog, UserLoginLog, OperateLog, PasswordChangeLog,
|
||||
ActivityLog, JobLog,
|
||||
ActivityLog, JobLog, UserSession
|
||||
)
|
||||
from .serializers import (
|
||||
FTPLogSerializer, UserLoginLogSerializer, JobLogSerializer,
|
||||
OperateLogSerializer, OperateLogActionDetailSerializer,
|
||||
PasswordChangeLogSerializer, ActivityUnionLogSerializer,
|
||||
FileSerializer
|
||||
FileSerializer, UserSessionSerializer
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
@ -246,3 +250,41 @@ class PasswordChangeLogViewSet(OrgReadonlyModelViewSet):
|
|||
user__in=[str(user) for user in users]
|
||||
)
|
||||
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)
|
||||
|
|
|
@ -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')],
|
||||
},
|
||||
),
|
||||
]
|
|
@ -28,7 +28,8 @@ __all__ = [
|
|||
"ActivityLog",
|
||||
"PasswordChangeLog",
|
||||
"UserLoginLog",
|
||||
"JobLog"
|
||||
"JobLog",
|
||||
"UserSession"
|
||||
]
|
||||
|
||||
|
||||
|
@ -245,3 +246,36 @@ class UserLoginLog(models.Model):
|
|||
class Meta:
|
||||
ordering = ["-datetime", "username"]
|
||||
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')),
|
||||
]
|
||||
|
|
|
@ -4,12 +4,13 @@ from django.utils.translation import gettext_lazy as _
|
|||
from rest_framework import serializers
|
||||
|
||||
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.timezone import as_current_tz
|
||||
from ops.serializers.job import JobExecutionSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from terminal.models import Session
|
||||
from users.models import User
|
||||
from . import models
|
||||
from .const import (
|
||||
ActionChoices, OperateChoices,
|
||||
|
@ -163,3 +164,27 @@ class ActivityUnionLogSerializer(serializers.Serializer):
|
|||
|
||||
class FileSerializer(serializers.Serializer):
|
||||
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
|
||||
|
|
|
@ -16,8 +16,9 @@ from audits.models import UserLoginLog
|
|||
from authentication.signals import post_auth_failed, post_auth_success
|
||||
from authentication.utils import check_different_city_login_if_need
|
||||
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 ..models import UserSession
|
||||
from ..utils import write_login_log
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
|
@ -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'job-logs', api.JobAuditViewSet, 'job-log')
|
||||
router.register(r'my-login-logs', api.MyLoginLogViewSet, 'my-login-log')
|
||||
router.register(r'user-sessions', api.UserSessionViewSet, 'user-session')
|
||||
|
||||
urlpatterns = [
|
||||
path('activities/', api.ResourceActivityAPIView.as_view(), name='resource-activities'),
|
||||
|
|
|
@ -36,7 +36,6 @@ system_user_perms += (user_perms + _view_all_joined_org_perms)
|
|||
|
||||
_auditor_perms = (
|
||||
('rbac', 'menupermission', 'view', 'audit'),
|
||||
('users', 'usersession', '*', '*'),
|
||||
('audits', '*', '*', '*'),
|
||||
('audits', 'joblog', '*', '*'),
|
||||
('terminal', 'commandstorage', 'view', 'commandstorage'),
|
||||
|
|
|
@ -28,7 +28,6 @@ exclude_permissions = (
|
|||
('authentication', 'superconnectiontoken', 'change,delete', 'superconnectiontoken'),
|
||||
('authentication', 'temptoken', 'delete', 'temptoken'),
|
||||
('users', 'userpasswordhistory', '*', '*'),
|
||||
('users', 'usersession', 'add,delete,change', 'usersession'),
|
||||
('assets', 'adminuser', '*', '*'),
|
||||
('assets', 'assetgroup', '*', '*'),
|
||||
('assets', 'cluster', '*', '*'),
|
||||
|
@ -93,6 +92,7 @@ exclude_permissions = (
|
|||
('audits', 'activitylog', 'add,delete,change', 'activitylog'),
|
||||
('audits', 'passwordchangelog', 'add,change,delete', 'passwordchangelog'),
|
||||
('audits', 'userloginlog', 'add,change,delete,change', 'userloginlog'),
|
||||
('audits', 'usersession', 'add,delete,change', 'usersession'),
|
||||
('audits', 'ftplog', 'delete', 'ftplog'),
|
||||
('tickets', 'ticketassignee', '*', 'ticketassignee'),
|
||||
('tickets', 'ticketflow', 'add,delete', 'ticketflow'),
|
||||
|
|
|
@ -98,7 +98,6 @@ special_pid_mapper = {
|
|||
'terminal.endpoint': 'terminal_node',
|
||||
'terminal.endpointrule': 'terminal_node',
|
||||
'audits.ftplog': 'terminal',
|
||||
'users.usersession': 'terminal',
|
||||
'perms.view_myassets': 'my_assets',
|
||||
'ops.celerytask': 'task_center',
|
||||
'ops.view_celerytaskexecution': 'task_center',
|
||||
|
@ -113,6 +112,7 @@ special_pid_mapper = {
|
|||
"settings.view_setting": "view_setting",
|
||||
"rbac.view_console": "view_console",
|
||||
"rbac.view_audit": "view_audit",
|
||||
'audits.usersession': 'view_audit',
|
||||
"rbac.view_workbench": "view_workbench",
|
||||
"rbac.view_webterminal": "view_workbench",
|
||||
"rbac.view_filemanager": "view_workbench",
|
||||
|
|
|
@ -6,5 +6,4 @@ from .preference import *
|
|||
from .profile import *
|
||||
from .relation import *
|
||||
from .service import *
|
||||
from .session import *
|
||||
from .user import *
|
||||
|
|
|
@ -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)
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -4,6 +4,5 @@
|
|||
|
||||
from .group import *
|
||||
from .preference import *
|
||||
from .session import *
|
||||
from .user import *
|
||||
from .utils import *
|
||||
|
|
|
@ -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')),
|
||||
]
|
|
@ -4,5 +4,4 @@ from .group import *
|
|||
from .preference import *
|
||||
from .profile import *
|
||||
from .realtion import *
|
||||
from .session import *
|
||||
from .user import *
|
||||
|
|
|
@ -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
|
||||
|
|
@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from django_auth_ldap.backend import populate_user
|
||||
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.oidc.signals import openid_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 jumpserver.utils import get_current_request
|
||||
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
|
||||
|
||||
logger = get_logger(__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'service-account-registrations', api.ServiceAccountRegistrationViewSet, 'service-account-registration')
|
||||
router.register(r'connection-token', auth_api.ConnectionTokenViewSet, 'connection-token')
|
||||
router.register(r'user-sessions', api.UserSessionViewSet, 'user-session')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
|
Loading…
Reference in New Issue