From db99ab80dbdee383de235bc32d208568ee2cbcf4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 10 Jun 2021 18:02:55 +0800 Subject: [PATCH 1/5] =?UTF-8?q?perf(auth):=20=E6=8E=88=E6=9D=83token?= =?UTF-8?q?=E5=BD=A2=E5=BC=8F=E7=99=BB=E5=BD=95=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E7=99=BB=E5=BD=95=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/api.py | 10 ++++++++-- apps/audits/serializers.py | 2 +- apps/audits/signals_handler.py | 12 +++++++----- apps/authentication/api/connection_token.py | 18 +++++++++++++----- apps/authentication/backends/api.py | 8 ++++++++ apps/jumpserver/settings/auth.py | 4 +++- 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index 83caf0db9..ed44bdc1a 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -94,8 +94,14 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) ] - filterset_fields = ['user__name', 'command', 'run_as__name', 'is_finished'] - search_fields = ['command', 'user__name', 'run_as__name'] + filterset_fields = [ + 'user__name', 'user__username', 'command', + 'run_as__name', 'run_as__username', 'is_finished' + ] + search_fields = [ + 'command', 'user__name', 'user__username', + 'run_as__name', 'run_as__username', + ] ordering = ['-date_created'] def get_queryset(self): diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index d7ab3e996..7bab342ee 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -82,7 +82,7 @@ class CommandExecutionSerializer(serializers.ModelSerializer): model = CommandExecution fields_mini = ['id'] fields_small = fields_mini + [ - 'run_as', 'command', 'user', 'is_finished', + 'run_as', 'command', 'is_finished', 'user', 'date_start', 'result', 'is_success', 'org_id' ] fields = fields_small + ['hosts', 'hosts_display', 'run_as_display', 'user_display'] diff --git a/apps/audits/signals_handler.py b/apps/audits/signals_handler.py index 930a1f03d..5f94fb42a 100644 --- a/apps/audits/signals_handler.py +++ b/apps/audits/signals_handler.py @@ -57,6 +57,7 @@ class AuthBackendLabelMapping(LazyObject): backend_label_mapping[settings.AUTH_BACKEND_PUBKEY] = _('SSH Key') backend_label_mapping[settings.AUTH_BACKEND_MODEL] = _('Password') backend_label_mapping[settings.AUTH_BACKEND_SSO] = _('SSO') + backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _('Auth Token') backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _('WeCom') backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _('DingTalk') return backend_label_mapping @@ -156,12 +157,13 @@ def get_login_backend(request): return backend_label -def generate_data(username, request): +def generate_data(username, request, login_type=None): user_agent = request.META.get('HTTP_USER_AGENT', '') login_ip = get_request_ip(request) or '0.0.0.0' - if isinstance(request, Request): + + if login_type is None and isinstance(request, Request): login_type = request.META.get('HTTP_X_JMS_LOGIN_TYPE', 'U') - else: + if login_type is None: login_type = 'W' data = { @@ -176,9 +178,9 @@ def generate_data(username, request): @receiver(post_auth_success) -def on_user_auth_success(sender, user, request, **kwargs): +def on_user_auth_success(sender, user, request, login_type=None, **kwargs): logger.debug('User login success: {}'.format(user.username)) - data = generate_data(user.username, request) + data = generate_data(user.username, request, login_type=login_type) data.update({'mfa': int(user.mfa_enabled), 'status': True}) write_login_log(**data) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 76d6e934d..e9678392d 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -6,12 +6,14 @@ from django.conf import settings from django.core.cache import cache from django.shortcuts import get_object_or_404 from django.http import HttpResponse +from django.utils.translation import ugettext as _ from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied from rest_framework import serializers +from authentication.signals import post_auth_failed, post_auth_success from common.utils import get_logger, random_string from common.drf.api import SerializerMixin2 from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser @@ -51,10 +53,6 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView return True def create_token(self, user, asset, application, system_user, ttl=5*60): - if not settings.CONNECTION_TOKEN_ENABLED: - raise PermissionDenied('Connection token disabled') - if not user: - user = self.request.user if not self.request.user.is_superuser and user != self.request.user: raise PermissionDenied('Only super user can create user token') self.check_resource_permission(user, asset, application, system_user) @@ -238,7 +236,14 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView @action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail') def get_secret_detail(self, request, *args, **kwargs): token = request.data.get('token', '') - value, user, system_user, asset, app = self.valid_token(token) + try: + value, user, system_user, asset, app = self.valid_token(token) + except serializers.ValidationError as e: + post_auth_failed.send( + sender=self.__class__, username='', request=self.request, + reason=_('Invalid token') + ) + raise e data = dict(user=user, system_user=system_user) if asset: @@ -252,6 +257,9 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView data['type'] = 'application' data.update(app_detail) + self.request.session['auth_backend'] = settings.AUTH_BACKEND_AUTH_TOKEN + post_auth_success.send(sender=self.__class__, user=user, request=self.request, login_type='T') + serializer = self.get_serializer(data) return Response(data=serializer.data, status=200) diff --git a/apps/authentication/backends/api.py b/apps/authentication/backends/api.py index 9da3bbbc3..308c441a2 100644 --- a/apps/authentication/backends/api.py +++ b/apps/authentication/backends/api.py @@ -228,3 +228,11 @@ class DingTalkAuthentication(ModelBackend): def authenticate(self, request, **kwargs): pass + + +class AuthorizationTokenAuthentication(ModelBackend): + """ + 什么也不做呀😺 + """ + def authenticate(self, request, **kwargs): + pass diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 502146f13..a4b2fb296 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -130,10 +130,12 @@ AUTH_BACKEND_CAS = 'authentication.backends.cas.CASBackend' AUTH_BACKEND_SSO = 'authentication.backends.api.SSOAuthentication' AUTH_BACKEND_WECOM = 'authentication.backends.api.WeComAuthentication' AUTH_BACKEND_DINGTALK = 'authentication.backends.api.DingTalkAuthentication' +AUTH_BACKEND_AUTH_TOKEN = 'authentication.backends.api.AuthorizationTokenAuthentication' AUTHENTICATION_BACKENDS = [ - AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_WECOM, AUTH_BACKEND_DINGTALK + AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_WECOM, + AUTH_BACKEND_DINGTALK, AUTH_BACKEND_AUTH_TOKEN ] if AUTH_CAS: From 8d3c1bd783f7a96e96c030df77cb6bfb2e37d03f Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 10 Jun 2021 19:45:03 +0800 Subject: [PATCH 2/5] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=8E=B7?= =?UTF-8?q?=E5=8F=96token=20secret,=20=E9=87=8D=E6=96=B0=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index e9678392d..1ac5435be 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -231,6 +231,11 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView if asset and not asset.is_active: raise serializers.ValidationError("Asset disabled") + + try: + self.check_resource_permission(user, asset, app, system_user) + except PermissionDenied: + raise serializers.ValidationError('Permission expired or invalid') return value, user, system_user, asset, app @action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail') From 97a0e27307a4a4c2b9b408ee3e6594cb3f13961a Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 11 Jun 2021 17:11:29 +0800 Subject: [PATCH 3/5] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E4=B8=AD=E5=BF=83=E6=9C=AA=E8=AF=BB=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/routing.py | 4 +- apps/jumpserver/settings/base.py | 2 +- apps/jumpserver/urls.py | 2 +- apps/notifications/api/site_msgs.py | 12 ++-- apps/notifications/apps.py | 4 ++ apps/notifications/backends/site_msg.py | 2 +- apps/notifications/migrations/0001_initial.py | 2 +- apps/notifications/signals_handler.py | 43 ++++++++++++ apps/notifications/site_msg.py | 43 ++++++------ .../urls/{notifications.py => api_urls.py} | 0 apps/notifications/urls/ws_urls.py | 9 +++ apps/notifications/ws.py | 70 +++++++++++++++++++ 12 files changed, 161 insertions(+), 32 deletions(-) create mode 100644 apps/notifications/signals_handler.py rename apps/notifications/urls/{notifications.py => api_urls.py} (100%) create mode 100644 apps/notifications/urls/ws_urls.py create mode 100644 apps/notifications/ws.py diff --git a/apps/jumpserver/routing.py b/apps/jumpserver/routing.py index d76f1ccee..5deae804e 100644 --- a/apps/jumpserver/routing.py +++ b/apps/jumpserver/routing.py @@ -2,9 +2,11 @@ from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter from ops.urls.ws_urls import urlpatterns as ops_urlpatterns +from notifications.urls.ws_urls import urlpatterns as notifications_urlpatterns urlpatterns = [] -urlpatterns += ops_urlpatterns +urlpatterns += ops_urlpatterns \ + + notifications_urlpatterns application = ProtocolTypeRouter({ 'websocket': AuthMiddlewareStack( diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 268bafa44..ef69cec8e 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -48,7 +48,7 @@ INSTALLED_APPS = [ 'applications.apps.ApplicationsConfig', 'tickets.apps.TicketsConfig', 'acls.apps.AclsConfig', - 'notifications', + 'notifications.apps.NotificationsConfig', 'common.apps.CommonConfig', 'jms_oidc_rp', 'rest_framework', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index c2ffea6ec..43d0e6cb0 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -23,7 +23,7 @@ api_v1 = [ path('applications/', include('applications.urls.api_urls', namespace='api-applications')), path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')), path('acls/', include('acls.urls.api_urls', namespace='api-acls')), - path('notifications/', include('notifications.urls.notifications', namespace='api-notifications')), + path('notifications/', include('notifications.urls.api_urls', namespace='api-notifications')), path('prometheus/metrics/', api.PrometheusMetricsApi.as_view()), ] diff --git a/apps/notifications/api/site_msgs.py b/apps/notifications/api/site_msgs.py index 6ee856922..2f8ba7e15 100644 --- a/apps/notifications/api/site_msgs.py +++ b/apps/notifications/api/site_msgs.py @@ -10,7 +10,7 @@ from ..serializers import ( SiteMessageDetailSerializer, SiteMessageIdsSerializer, SiteMessageSendSerializer, ) -from ..site_msg import SiteMessage +from ..site_msg import SiteMessageUtil from ..filters import SiteMsgFilter __all__ = ('SiteMessageViewSet', ) @@ -30,15 +30,15 @@ class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JmsGenericViewSet): has_read = self.request.query_params.get('has_read') if has_read is None: - msgs = SiteMessage.get_user_all_msgs(user.id) + msgs = SiteMessageUtil.get_user_all_msgs(user.id) else: - msgs = SiteMessage.filter_user_msgs(user.id, has_read=is_true(has_read)) + msgs = SiteMessageUtil.filter_user_msgs(user.id, has_read=is_true(has_read)) return msgs @action(methods=[GET], detail=False, url_path='unread-total') def unread_total(self, request, **kwargs): user = request.user - msgs = SiteMessage.filter_user_msgs(user.id, has_read=False) + msgs = SiteMessageUtil.filter_user_msgs(user.id, has_read=False) return Response(data={'total': msgs.count()}) @action(methods=[PATCH], detail=False, url_path='mark-as-read') @@ -47,12 +47,12 @@ class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JmsGenericViewSet): seri = self.get_serializer(data=request.data) seri.is_valid(raise_exception=True) ids = seri.validated_data['ids'] - SiteMessage.mark_msgs_as_read(user.id, ids) + SiteMessageUtil.mark_msgs_as_read(user.id, ids) return Response({'detail': 'ok'}) @action(methods=[POST], detail=False) def send(self, request, **kwargs): seri = self.get_serializer(data=request.data) seri.is_valid(raise_exception=True) - SiteMessage.send_msg(**seri.validated_data, sender=request.user) + SiteMessageUtil.send_msg(**seri.validated_data, sender=request.user) return Response({'detail': 'ok'}) diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py index 9c260e0b1..f14a8ebe9 100644 --- a/apps/notifications/apps.py +++ b/apps/notifications/apps.py @@ -3,3 +3,7 @@ from django.apps import AppConfig class NotificationsConfig(AppConfig): name = 'notifications' + + def ready(self): + from . import signals_handler + super().ready() diff --git a/apps/notifications/backends/site_msg.py b/apps/notifications/backends/site_msg.py index 33032843a..0f7468f48 100644 --- a/apps/notifications/backends/site_msg.py +++ b/apps/notifications/backends/site_msg.py @@ -1,4 +1,4 @@ -from notifications.site_msg import SiteMessage as Client +from notifications.site_msg import SiteMessageUtil as Client from .base import BackendBase diff --git a/apps/notifications/migrations/0001_initial.py b/apps/notifications/migrations/0001_initial.py index ebe79f304..8e55bb305 100644 --- a/apps/notifications/migrations/0001_initial.py +++ b/apps/notifications/migrations/0001_initial.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='SiteMessage', + name='SiteMessageUtil', fields=[ ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), diff --git a/apps/notifications/signals_handler.py b/apps/notifications/signals_handler.py new file mode 100644 index 000000000..13ebdc4bc --- /dev/null +++ b/apps/notifications/signals_handler.py @@ -0,0 +1,43 @@ +import json + +from django.utils.functional import LazyObject +from django.db.models.signals import post_save +from django.dispatch import receiver + +from common.utils.connection import RedisPubSub +from common.utils import get_logger +from common.decorator import on_transaction_commit +from .models import SiteMessage + + +logger = get_logger(__name__) + + +def new_site_msg_pub_sub(): + return RedisPubSub('notifications.SiteMessageCome') + + +class NewSiteMsgSubPub(LazyObject): + def _setup(self): + self._wrapped = new_site_msg_pub_sub() + + +new_site_msg_chan = NewSiteMsgSubPub() + + +@receiver(post_save, sender=SiteMessage) +@on_transaction_commit +def on_site_message_create(sender, instance, created, **kwargs): + if not created: + return + logger.debug('New site msg created, publish it') + user_ids = instance.users.all().values_list('id', flat=True) + user_ids = [str(i) for i in user_ids] + data = { + 'id': str(instance.id), + 'subject': instance.subject, + 'message': instance.message, + 'users': user_ids + } + data = json.dumps(data) + new_site_msg_chan.publish(data) diff --git a/apps/notifications/site_msg.py b/apps/notifications/site_msg.py index b78d3c7f4..1a5c9dc23 100644 --- a/apps/notifications/site_msg.py +++ b/apps/notifications/site_msg.py @@ -1,11 +1,12 @@ from django.db.models import F +from django.db import transaction from common.utils.timezone import now from users.models import User from .models import SiteMessage as SiteMessageModel, SiteMessageUsers -class SiteMessage: +class SiteMessageUtil: @classmethod def send_msg(cls, subject, message, user_ids=(), group_ids=(), @@ -13,24 +14,24 @@ class SiteMessage: if not any((user_ids, group_ids, is_broadcast)): raise ValueError('No recipient is specified') - site_msg = SiteMessageModel.objects.create( - subject=subject, message=message, - is_broadcast=is_broadcast, sender=sender, - ) + with transaction.atomic(): + site_msg = SiteMessageModel.objects.create( + subject=subject, message=message, + is_broadcast=is_broadcast, sender=sender, + ) - if is_broadcast: - user_ids = User.objects.all().values_list('id', flat=True) - else: - if group_ids: - site_msg.groups.add(*group_ids) + if is_broadcast: + user_ids = User.objects.all().values_list('id', flat=True) + else: + if group_ids: + site_msg.groups.add(*group_ids) - user_ids_from_group = User.groups.through.objects.filter( - usergroup_id__in=group_ids - ).values_list('user_id', flat=True) + user_ids_from_group = User.groups.through.objects.filter( + usergroup_id__in=group_ids + ).values_list('user_id', flat=True) + user_ids = [*user_ids, *user_ids_from_group] - user_ids = [*user_ids, *user_ids_from_group] - - site_msg.users.add(*user_ids) + site_msg.users.add(*user_ids) @classmethod def get_user_all_msgs(cls, user_id): @@ -72,14 +73,14 @@ class SiteMessage: @classmethod def mark_msgs_as_read(cls, user_id, msg_ids): - sitemsg_users = SiteMessageUsers.objects.filter( + site_msg_users = SiteMessageUsers.objects.filter( user_id=user_id, sitemessage_id__in=msg_ids, has_read=False ) - for sitemsg_user in sitemsg_users: - sitemsg_user.has_read = True - sitemsg_user.read_at = now() + for site_msg_user in site_msg_users: + site_msg_user.has_read = True + site_msg_user.read_at = now() SiteMessageUsers.objects.bulk_update( - sitemsg_users, fields=('has_read', 'read_at')) + site_msg_users, fields=('has_read', 'read_at')) diff --git a/apps/notifications/urls/notifications.py b/apps/notifications/urls/api_urls.py similarity index 100% rename from apps/notifications/urls/notifications.py rename to apps/notifications/urls/api_urls.py diff --git a/apps/notifications/urls/ws_urls.py b/apps/notifications/urls/ws_urls.py new file mode 100644 index 000000000..dfd457e52 --- /dev/null +++ b/apps/notifications/urls/ws_urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from .. import ws + +app_name = 'notifications' + +urlpatterns = [ + path('ws/notifications/site-msg/', ws.SiteMsgWebsocket, name='site-msg-ws'), +] \ No newline at end of file diff --git a/apps/notifications/ws.py b/apps/notifications/ws.py new file mode 100644 index 000000000..cbbb25d2d --- /dev/null +++ b/apps/notifications/ws.py @@ -0,0 +1,70 @@ +import threading +import json + +from channels.generic.websocket import JsonWebsocketConsumer + +from common.utils import get_logger +from .models import SiteMessage +from .site_msg import SiteMessageUtil +from .signals_handler import new_site_msg_chan + +logger = get_logger(__name__) + + +class SiteMsgWebsocket(JsonWebsocketConsumer): + disconnected = False + refresh_every_seconds = 10 + + def connect(self): + user = self.scope["user"] + if user.is_authenticated: + self.accept() + + thread = threading.Thread(target=self.unread_site_msg_count) + thread.start() + else: + self.close() + + def receive(self, text_data=None, bytes_data=None, **kwargs): + data = json.loads(text_data) + refresh_every_seconds = data.get('refresh_every_seconds') + + try: + refresh_every_seconds = int(refresh_every_seconds) + except Exception as e: + logger.error(e) + return + + if refresh_every_seconds > 0: + self.refresh_every_seconds = refresh_every_seconds + + def send_unread_msg_count(self): + user_id = self.scope["user"].id + unread_count = SiteMessageUtil.get_user_unread_msgs_count(user_id) + logger.debug('Send unread count to user: {} {}'.format(user_id, unread_count)) + self.send_json({'type': 'unread_count', 'unread_count': unread_count}) + + def unread_site_msg_count(self): + user_id = str(self.scope["user"].id) + self.send_unread_msg_count() + + while not self.disconnected: + subscribe = new_site_msg_chan.subscribe() + for message in subscribe.listen(): + if message['type'] != 'message': + continue + try: + msg = json.loads(message['data'].decode()) + logger.debug('New site msg recv, may be mine: {}'.format(msg)) + if not msg: + continue + users = msg.get('users', []) + logger.debug('Message users: {}'.format(users)) + if user_id in users: + self.send_unread_msg_count() + except json.JSONDecoder as e: + logger.debug('Decode json error: ', e) + + def disconnect(self, close_code): + self.disconnected = True + self.close() From 00e4c3cd07132f4551f5078dd6096b0d20553980 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 11 Jun 2021 11:05:40 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E7=94=A8=E6=88=B7API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/__init__.py | 1 + apps/applications/api/application.py | 37 ++----------- apps/applications/api/application_user.py | 55 ++++++++++++++++++++ apps/applications/hands.py | 2 +- apps/applications/models/application.py | 7 ++- apps/applications/serializers/application.py | 42 ++++++++++++++- apps/applications/urls/api_urls.py | 3 +- 7 files changed, 109 insertions(+), 38 deletions(-) create mode 100644 apps/applications/api/application_user.py diff --git a/apps/applications/api/__init__.py b/apps/applications/api/__init__.py index 68f0d1803..a11b4966a 100644 --- a/apps/applications/api/__init__.py +++ b/apps/applications/api/__init__.py @@ -1,3 +1,4 @@ from .application import * +from .application_user import * from .mixin import * from .remote_app import * diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index 84426b6b2..867a8ddd3 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -2,18 +2,13 @@ # from orgs.mixins.api import OrgBulkModelViewSet -from rest_framework import generics -from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin -from .. import models, serializers +from ..hands import IsOrgAdminOrAppUser +from .. import serializers from ..models import Application -from assets.models import SystemUser -from assets.serializers import SystemUserListSerializer -from perms.models import ApplicationPermission -from ..const import ApplicationCategoryChoices -__all__ = ['ApplicationViewSet', 'ApplicationUserListApi'] +__all__ = ['ApplicationViewSet'] class ApplicationViewSet(OrgBulkModelViewSet): @@ -22,29 +17,3 @@ class ApplicationViewSet(OrgBulkModelViewSet): search_fields = filterset_fields permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.ApplicationSerializer - - -class ApplicationUserListApi(generics.ListAPIView): - permission_classes = (IsOrgAdmin, ) - filterset_fields = ('name', 'username') - search_fields = filterset_fields - serializer_class = SystemUserListSerializer - - def get_application(self): - application = None - app_id = self.request.query_params.get('application_id') - if app_id: - application = Application.objects.get(id=app_id) - return application - - def get_queryset(self): - queryset = SystemUser.objects.none() - application = self.get_application() - if not application: - return queryset - system_user_ids = ApplicationPermission.objects.filter(applications=application)\ - .values_list('system_users', flat=True) - if not system_user_ids: - return queryset - queryset = SystemUser.objects.filter(id__in=system_user_ids) - return queryset diff --git a/apps/applications/api/application_user.py b/apps/applications/api/application_user.py new file mode 100644 index 000000000..93bbc29a5 --- /dev/null +++ b/apps/applications/api/application_user.py @@ -0,0 +1,55 @@ +# coding: utf-8 +# + +from rest_framework import generics +from django.conf import settings + +from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify +from .. import serializers +from ..models import Application, ApplicationUser +from perms.models import ApplicationPermission + + +class ApplicationUserListApi(generics.ListAPIView): + permission_classes = (IsOrgAdmin, ) + filterset_fields = ('name', 'username') + search_fields = filterset_fields + serializer_class = serializers.ApplicationUserSerializer + _application = None + + @property + def application(self): + if self._application is None: + app_id = self.request.query_params.get('application_id') + if app_id: + self._application = Application.objects.get(id=app_id) + return self._application + + def get_serializer_context(self): + context = super().get_serializer_context() + context.update({ + 'application': self.application + }) + return context + + def get_queryset(self): + queryset = ApplicationUser.objects.none() + if not self.application: + return queryset + system_user_ids = ApplicationPermission.objects.filter(applications=self.application)\ + .values_list('system_users', flat=True) + if not system_user_ids: + return queryset + queryset = ApplicationUser.objects.filter(id__in=system_user_ids) + return queryset + + +class ApplicationUserAuthInfoListApi(ApplicationUserListApi): + serializer_class = serializers.ApplicationUserWithAuthInfoSerializer + http_method_names = ['get'] + permission_classes = [IsOrgAdminOrAppUser] + + def get_permissions(self): + if settings.SECURITY_VIEW_AUTH_NEED_MFA: + self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] + return super().get_permissions() diff --git a/apps/applications/hands.py b/apps/applications/hands.py index ee13e589e..4987d5948 100644 --- a/apps/applications/hands.py +++ b/apps/applications/hands.py @@ -11,5 +11,5 @@ """ -from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser +from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser, NeedMFAVerify from users.models import User, UserGroup diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index f7b541580..c9374181d 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.models import OrgModelMixin from common.mixins import CommonModelMixin -from assets.models import Asset +from assets.models import Asset, SystemUser from .. import const @@ -68,3 +68,8 @@ class Application(CommonModelMixin, OrgModelMixin): raise ValueError("Remote App not has asset attr") asset = Asset.objects.filter(id=asset_id).first() return asset + + +class ApplicationUser(SystemUser): + class Meta: + proxy = True diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index bff8c270a..94c55c1a8 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -6,11 +6,12 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import MethodSerializer from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping - +from assets.serializers import SystemUserSerializer from .. import models __all__ = [ 'ApplicationSerializer', 'ApplicationSerializerMixin', + 'ApplicationUserSerializer', 'ApplicationUserWithAuthInfoSerializer' ] @@ -66,3 +67,42 @@ class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSeri _attrs.update(attrs) return _attrs + +class ApplicationUserSerializer(SystemUserSerializer): + application_name = serializers.SerializerMethodField(label=_('Application name')) + application_category = serializers.SerializerMethodField(label=_('Application category')) + application_type = serializers.SerializerMethodField(label=_('Application type')) + + class Meta(SystemUserSerializer.Meta): + model = models.ApplicationUser + fields_mini = [ + 'id', 'application_name', 'application_category', 'application_type', 'name', 'username' + ] + fields_small = fields_mini + [ + 'protocol', 'login_mode', 'login_mode_display', 'priority', + "username_same_with_user", 'comment', + ] + fields = fields_small + extra_kwargs = { + 'login_mode_display': {'label': _('Login mode display')}, + 'created_by': {'read_only': True}, + } + + @property + def application(self): + return self.context['application'] + + def get_application_name(self, obj): + return self.application.name + + def get_application_category(self, obj): + return self.application.get_category_display() + + def get_application_type(self, obj): + return self.application.get_type_display() + + +class ApplicationUserWithAuthInfoSerializer(ApplicationUserSerializer): + + class Meta(ApplicationUserSerializer.Meta): + fields = ApplicationUserSerializer.Meta.fields + ['password'] diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py index 9ca50d32c..fb5d08228 100644 --- a/apps/applications/urls/api_urls.py +++ b/apps/applications/urls/api_urls.py @@ -14,7 +14,8 @@ router.register(r'applications', api.ApplicationViewSet, 'application') urlpatterns = [ path('remote-apps//connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'), - path('application-users/', api.ApplicationUserListApi.as_view(), name='application-user') + path('application-users/', api.ApplicationUserListApi.as_view(), name='application-user'), + path('application-user-auth-infos/', api.ApplicationUserAuthInfoListApi.as_view(), name='application-user-auth-info') ] From e0604a321105ce6b4c6050d3d045b22f44060dde Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 11 Jun 2021 11:07:47 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 76178 -> 76320 bytes apps/locale/zh/LC_MESSAGES/django.po | 205 ++++++++++++++------------- 2 files changed, 109 insertions(+), 96 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index a3df15a78817b6aa1bf9cb981eb14789748326e6..f1e5b13b4213a15171250e3f370260565640b52d 100644 GIT binary patch delta 22220 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 L+OYiWt@{55>n+6> delta 22127 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 C!Hl^8 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index c6787d382..0acdf61d9 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-06-04 11:29+0800\n" +"POT-Creation-Date: 2021-06-11 11:06+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -121,7 +121,7 @@ msgstr "系统用户" #: applications/serializers/attrs/application_category/remote_app.py:33 #: assets/models/asset.py:355 assets/models/authbook.py:26 #: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:34 -#: assets/serializers/asset_user.py:48 assets/serializers/asset_user.py:90 +#: assets/serializers/asset_user.py:48 assets/serializers/asset_user.py:91 #: assets/serializers/system_user.py:202 audits/models.py:38 #: perms/models/asset_permission.py:99 templates/index.html:82 #: terminal/backends/command/models.py:19 @@ -158,7 +158,7 @@ msgstr "" #: acls/serializers/login_acl.py:30 acls/serializers/login_asset_acl.py:31 #: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: assets/models/asset.py:183 assets/models/domain.py:52 -#: assets/serializers/asset_user.py:47 settings/serializers/settings.py:117 +#: assets/serializers/asset_user.py:47 settings/serializers/settings.py:113 #: users/templates/users/_granted_assets.html:26 #: users/templates/users/user_asset_permission.html:156 msgid "IP" @@ -199,7 +199,7 @@ msgstr "" #: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:184 #: assets/serializers/asset_user.py:46 assets/serializers/gathered_user.py:23 -#: settings/serializers/settings.py:116 +#: settings/serializers/settings.py:112 #: users/templates/users/_granted_assets.html:25 #: users/templates/users/user_asset_permission.html:157 msgid "Hostname" @@ -272,14 +272,32 @@ msgstr "网域" msgid "Attrs" msgstr "" -#: applications/serializers/application.py:47 +#: applications/serializers/application.py:48 msgid "Category(Display)" msgstr "类别 (显示名称)" -#: applications/serializers/application.py:48 +#: applications/serializers/application.py:49 msgid "Type(Dispaly)" msgstr "类型 (显示名称)" +#: applications/serializers/application.py:72 +msgid "Application name" +msgstr "应用名称" + +#: applications/serializers/application.py:73 +msgid "Application category" +msgstr "应用类别" + +#: applications/serializers/application.py:74 +msgid "Application type" +msgstr "应用类型" + +#: applications/serializers/application.py:87 +#: assets/serializers/system_user.py:49 assets/serializers/system_user.py:177 +#: assets/serializers/system_user.py:203 +msgid "Login mode display" +msgstr "登录模式(显示名称)" + #: applications/serializers/attrs/application_category/cloud.py:9 #: assets/models/cluster.py:40 msgid "Cluster" @@ -316,10 +334,10 @@ msgstr "目标URL" #: applications/serializers/attrs/application_type/custom.py:25 #: applications/serializers/attrs/application_type/mysql_workbench.py:34 #: applications/serializers/attrs/application_type/vmware_client.py:30 -#: assets/models/base.py:251 assets/serializers/asset_user.py:77 +#: assets/models/base.py:251 assets/serializers/asset_user.py:78 #: audits/signals_handler.py:58 authentication/forms.py:22 #: authentication/templates/authentication/login.html:164 -#: settings/serializers/settings.py:98 users/forms/profile.py:21 +#: settings/serializers/settings.py:94 users/forms/profile.py:21 #: users/templates/users/user_otp_check_password.html:13 #: users/templates/users/user_password_update.html:43 #: users/templates/users/user_password_verify.html:18 @@ -822,12 +840,12 @@ msgstr "后端" msgid "Source" msgstr "来源" -#: assets/serializers/asset_user.py:81 users/forms/profile.py:160 +#: assets/serializers/asset_user.py:82 users/forms/profile.py:160 #: users/models/user.py:580 users/templates/users/user_password_update.html:48 msgid "Public key" msgstr "SSH公钥" -#: assets/serializers/asset_user.py:85 users/models/user.py:577 +#: assets/serializers/asset_user.py:86 users/models/user.py:577 msgid "Private key" msgstr "ssh私钥" @@ -875,11 +893,6 @@ msgstr "同级别节点名字不能重复" msgid "Nodes amount" msgstr "节点数量" -#: assets/serializers/system_user.py:49 assets/serializers/system_user.py:177 -#: assets/serializers/system_user.py:203 -msgid "Login mode display" -msgstr "登录模式(显示名称)" - #: assets/serializers/system_user.py:51 assets/serializers/system_user.py:179 msgid "Ad domain" msgstr "Ad 网域" @@ -2049,7 +2062,7 @@ msgstr "应用程序" msgid "Application permission" msgstr "应用管理" -#: perms/models/asset_permission.py:37 settings/serializers/settings.py:121 +#: perms/models/asset_permission.py:37 settings/serializers/settings.py:117 msgid "All" msgstr "全部" @@ -2194,27 +2207,19 @@ msgstr "当前站点URL" msgid "eg: http://dev.jumpserver.org:8080" msgstr "如: http://dev.jumpserver.org:8080" -#: settings/serializers/settings.py:19 -msgid "RDP address" -msgstr "RDP 地址" - -#: settings/serializers/settings.py:21 -msgid "RDP visit address, eg: dev.jumpserver.org:3389" -msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389" - -#: settings/serializers/settings.py:24 +#: settings/serializers/settings.py:20 msgid "User guide url" msgstr "用户向导URL" -#: settings/serializers/settings.py:25 +#: settings/serializers/settings.py:21 msgid "User first login update profile done redirect to it" msgstr "用户第一次登录,修改profile后重定向到地址, 可以是 wiki 或 其他说明文档" -#: settings/serializers/settings.py:28 +#: settings/serializers/settings.py:24 msgid "Forgot password url" msgstr "忘记密码URL" -#: settings/serializers/settings.py:29 +#: settings/serializers/settings.py:25 msgid "" "The forgot password url on login page, If you use ldap or cas external " "authentication, you can set it" @@ -2222,138 +2227,138 @@ msgstr "" "登录页面忘记密码URL, 如果使用了 LDAP, OPENID 等外部认证系统,可以自定义用户重" "置密码访问的地址" -#: settings/serializers/settings.py:33 +#: settings/serializers/settings.py:29 msgid "Global organization name" msgstr "全局组织名" -#: settings/serializers/settings.py:34 +#: settings/serializers/settings.py:30 msgid "The name of global organization to display" msgstr "全局组织的显示名称,默认为 全局组织" -#: settings/serializers/settings.py:41 +#: settings/serializers/settings.py:37 msgid "SMTP host" msgstr "SMTP 主机" -#: settings/serializers/settings.py:42 +#: settings/serializers/settings.py:38 msgid "SMTP port" msgstr "SMTP 端口" -#: settings/serializers/settings.py:43 +#: settings/serializers/settings.py:39 msgid "SMTP account" msgstr "SMTP 账号" -#: settings/serializers/settings.py:45 +#: settings/serializers/settings.py:41 msgid "SMTP password" msgstr "SMTP 密码" -#: settings/serializers/settings.py:46 +#: settings/serializers/settings.py:42 msgid "Tips: Some provider use token except password" msgstr "提示:一些邮件提供商需要输入的是授权码" -#: settings/serializers/settings.py:49 +#: settings/serializers/settings.py:45 msgid "Send user" msgstr "发件人" -#: settings/serializers/settings.py:50 +#: settings/serializers/settings.py:46 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号" -#: settings/serializers/settings.py:53 +#: settings/serializers/settings.py:49 msgid "Test recipient" msgstr "测试收件人" -#: settings/serializers/settings.py:54 +#: settings/serializers/settings.py:50 msgid "Tips: Used only as a test mail recipient" msgstr "提示:仅用来作为测试邮件收件人" -#: settings/serializers/settings.py:57 +#: settings/serializers/settings.py:53 msgid "Use SSL" msgstr "使用 SSL" -#: settings/serializers/settings.py:58 +#: settings/serializers/settings.py:54 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用 SSL" -#: settings/serializers/settings.py:61 +#: settings/serializers/settings.py:57 msgid "Use TLS" msgstr "使用 TLS" -#: settings/serializers/settings.py:62 +#: settings/serializers/settings.py:58 msgid "If SMTP port is 587, may be select" msgstr "如果SMTP端口是587,通常需要启用 TLS" -#: settings/serializers/settings.py:65 +#: settings/serializers/settings.py:61 msgid "Subject prefix" msgstr "主题前缀" -#: settings/serializers/settings.py:72 +#: settings/serializers/settings.py:68 msgid "Create user email subject" msgstr "邮件主题" -#: settings/serializers/settings.py:73 +#: settings/serializers/settings.py:69 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" -#: settings/serializers/settings.py:77 +#: settings/serializers/settings.py:73 msgid "Create user honorific" msgstr "邮件的敬语" -#: settings/serializers/settings.py:78 +#: settings/serializers/settings.py:74 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" -#: settings/serializers/settings.py:82 +#: settings/serializers/settings.py:78 msgid "Create user email content" msgstr "邮件的内容" -#: settings/serializers/settings.py:83 +#: settings/serializers/settings.py:79 msgid "Tips:When creating a user, send the content of the email" msgstr "提示: 创建用户时,发送设置密码邮件的内容" -#: settings/serializers/settings.py:86 +#: settings/serializers/settings.py:82 msgid "Signature" msgstr "署名" -#: settings/serializers/settings.py:87 +#: settings/serializers/settings.py:83 msgid "Tips: Email signature (eg:jumpserver)" msgstr "邮件署名 (如:jumpserver)" -#: settings/serializers/settings.py:95 +#: settings/serializers/settings.py:91 msgid "LDAP server" msgstr "LDAP 地址" -#: settings/serializers/settings.py:95 +#: settings/serializers/settings.py:91 msgid "eg: ldap://localhost:389" msgstr "" -#: settings/serializers/settings.py:97 +#: settings/serializers/settings.py:93 msgid "Bind DN" msgstr "绑定 DN" -#: settings/serializers/settings.py:100 +#: settings/serializers/settings.py:96 msgid "User OU" msgstr "用户 OU" -#: settings/serializers/settings.py:101 +#: settings/serializers/settings.py:97 msgid "Use | split multi OUs" msgstr "多个 OU 使用 | 分割" -#: settings/serializers/settings.py:104 +#: settings/serializers/settings.py:100 msgid "User search filter" msgstr "用户过滤器" -#: settings/serializers/settings.py:105 +#: settings/serializers/settings.py:101 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" -#: settings/serializers/settings.py:108 +#: settings/serializers/settings.py:104 msgid "User attr map" msgstr "用户属性映射" -#: settings/serializers/settings.py:109 +#: settings/serializers/settings.py:105 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -2361,23 +2366,23 @@ msgstr "" "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," "email 是jumpserver的用户需要属性" -#: settings/serializers/settings.py:111 +#: settings/serializers/settings.py:107 msgid "Enable LDAP auth" msgstr "启用 LDAP 认证" -#: settings/serializers/settings.py:122 +#: settings/serializers/settings.py:118 msgid "Auto" msgstr "自动" -#: settings/serializers/settings.py:128 +#: settings/serializers/settings.py:124 msgid "Password auth" msgstr "密码认证" -#: settings/serializers/settings.py:130 +#: settings/serializers/settings.py:126 msgid "Public key auth" msgstr "密钥认证" -#: settings/serializers/settings.py:131 +#: settings/serializers/settings.py:127 msgid "" "Tips: If use other auth method, like AD/LDAP, you should disable this to " "avoid being able to log in after deleting" @@ -2385,19 +2390,19 @@ msgstr "" "提示:如果你使用其它认证方式,如 AD/LDAP,你应该禁用此项,以避免第三方系统删" "除后,还可以登录" -#: settings/serializers/settings.py:134 +#: settings/serializers/settings.py:130 msgid "List sort by" msgstr "资产列表排序" -#: settings/serializers/settings.py:135 +#: settings/serializers/settings.py:131 msgid "List page size" msgstr "资产列表每页数量" -#: settings/serializers/settings.py:137 +#: settings/serializers/settings.py:133 msgid "Session keep duration" msgstr "会话日志保存时间" -#: settings/serializers/settings.py:138 +#: settings/serializers/settings.py:134 msgid "" "Units: days, Session, record, command will be delete if more than duration, " "only in database" @@ -2405,64 +2410,72 @@ msgstr "" "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" "受影响)" -#: settings/serializers/settings.py:140 +#: settings/serializers/settings.py:136 msgid "Telnet login regex" msgstr "Telnet 成功正则表达式" -#: settings/serializers/settings.py:145 +#: settings/serializers/settings.py:138 +msgid "RDP address" +msgstr "RDP 地址" + +#: settings/serializers/settings.py:141 +msgid "RDP visit address, eg: dev.jumpserver.org:3389" +msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389" + +#: settings/serializers/settings.py:147 msgid "Global MFA auth" msgstr "全局启用 MFA 认证" -#: settings/serializers/settings.py:146 +#: settings/serializers/settings.py:148 msgid "All user enable MFA" msgstr "强制所有用户启用多因子认证" -#: settings/serializers/settings.py:149 +#: settings/serializers/settings.py:151 msgid "Batch command execution" msgstr "批量命令执行" -#: settings/serializers/settings.py:150 +#: settings/serializers/settings.py:152 msgid "Allow user run batch command or not using ansible" msgstr "是否允许用户使用 ansible 执行批量命令" -#: settings/serializers/settings.py:153 +#: settings/serializers/settings.py:155 msgid "Enable terminal register" msgstr "终端注册" -#: settings/serializers/settings.py:154 +#: settings/serializers/settings.py:156 msgid "" "Allow terminal register, after all terminal setup, you should disable this " "for security" msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭" -#: settings/serializers/settings.py:158 +#: settings/serializers/settings.py:160 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: settings/serializers/settings.py:162 +#: settings/serializers/settings.py:164 msgid "Block logon interval" msgstr "禁止登录时间间隔" -#: settings/serializers/settings.py:163 +#: settings/serializers/settings.py:165 msgid "" "Tip: (unit/minute) if the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" -#: settings/serializers/settings.py:167 +#: settings/serializers/settings.py:169 msgid "Connection max idle time" msgstr "连接最大空闲时间" -#: settings/serializers/settings.py:168 +#: settings/serializers/settings.py:170 msgid "If idle time more than it, disconnect connection Unit: minute" msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" -#: settings/serializers/settings.py:172 +#: settings/serializers/settings.py:174 msgid "User password expiration" msgstr "用户密码过期时间" -#: settings/serializers/settings.py:173 +#: settings/serializers/settings.py:175 msgid "" "Tip: (unit: day) If the user does not update the password during the time, " "the user password will expire failure;The password expiration reminder mail " @@ -2472,53 +2485,53 @@ msgstr "" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" -#: settings/serializers/settings.py:177 +#: settings/serializers/settings.py:179 msgid "Number of repeated historical passwords" msgstr "不能设置近几次密码" -#: settings/serializers/settings.py:178 +#: settings/serializers/settings.py:180 msgid "" "Tip: When the user resets the password, it cannot be the previous n " "historical passwords of the user" msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码" -#: settings/serializers/settings.py:182 +#: settings/serializers/settings.py:184 msgid "Password minimum length" msgstr "密码最小长度" -#: settings/serializers/settings.py:185 +#: settings/serializers/settings.py:187 msgid "Must contain capital" msgstr "必须包含大写字符" -#: settings/serializers/settings.py:187 +#: settings/serializers/settings.py:189 msgid "Must contain lowercase" msgstr "必须包含小写字符" -#: settings/serializers/settings.py:188 +#: settings/serializers/settings.py:190 msgid "Must contain numeric" msgstr "必须包含数字" -#: settings/serializers/settings.py:189 +#: settings/serializers/settings.py:191 msgid "Must contain special" msgstr "必须包含特殊字符" -#: settings/serializers/settings.py:190 +#: settings/serializers/settings.py:192 msgid "Insecure command alert" msgstr "危险命令告警" -#: settings/serializers/settings.py:192 +#: settings/serializers/settings.py:194 msgid "Email recipient" msgstr "邮件收件人" -#: settings/serializers/settings.py:193 +#: settings/serializers/settings.py:195 msgid "Multiple user using , split" msgstr "多个用户,使用 , 分割" -#: settings/serializers/settings.py:201 +#: settings/serializers/settings.py:203 msgid "Enable WeCom Auth" msgstr "启用企业微信认证" -#: settings/serializers/settings.py:208 +#: settings/serializers/settings.py:210 msgid "Enable DingTalk Auth" msgstr "启用钉钉认证"