diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index ba3b069f5..a57cbdffc 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -9,7 +9,7 @@ import paramiko from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.utils import get_logger +from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin from .base import BaseUser @@ -36,7 +36,7 @@ class Domain(OrgModelMixin): def has_gateway(self): return self.gateway_set.filter(is_active=True).exists() - @property + @lazyproperty def gateways(self): return self.gateway_set.filter(is_active=True) @@ -44,8 +44,9 @@ class Domain(OrgModelMixin): gateways = [gw for gw in self.gateways if gw.is_connective] if gateways: return random.choice(gateways) - else: - logger.warn(f'Gateway all bad. domain={self}, gateway_num={len(self.gateways)}.') + + logger.warn(f'Gateway all bad. domain={self}, gateway_num={len(self.gateways)}.') + if self.gateways: return random.choice(self.gateways) diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index 9f20057a3..9ac565bc6 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -51,6 +51,7 @@ class AuthBackendLabelMapping(LazyObject): 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_FEISHU] = _('FeiShu') backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _('DingTalk') backend_label_mapping[settings.AUTH_BACKEND_TEMP_TOKEN] = _('Temporary token') return backend_label_mapping diff --git a/apps/authentication/api/confirm.py b/apps/authentication/api/confirm.py index c085bb017..d3a32a7c8 100644 --- a/apps/authentication/api/confirm.py +++ b/apps/authentication/api/confirm.py @@ -7,11 +7,18 @@ from rest_framework.generics import RetrieveAPIView, CreateAPIView from rest_framework.response import Response from rest_framework import status -from common.permissions import IsValidUser +from common.permissions import IsValidUser, UserConfirmation from ..const import ConfirmType from ..serializers import ConfirmSerializer +class ConfirmBindORUNBindOAuth(RetrieveAPIView): + permission_classes = (UserConfirmation.require(ConfirmType.ReLogin),) + + def retrieve(self, request, *args, **kwargs): + return Response('ok') + + class ConfirmApi(RetrieveAPIView, CreateAPIView): permission_classes = (IsValidUser,) serializer_class = ConfirmSerializer diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 828a93be5..f0a7e0a63 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -1,58 +1,61 @@ -# -*- coding: utf-8 -*- -# -import urllib.parse -import json -from typing import Callable import os +import json import base64 -import ctypes - -from django.core.cache import cache -from django.shortcuts import get_object_or_404 +import urllib.parse from django.http import HttpResponse -from django.utils import timezone -from django.utils.translation import ugettext as _ -from rest_framework.response import Response -from rest_framework.request import Request -from rest_framework.viewsets import GenericViewSet -from rest_framework.decorators import action +from django.shortcuts import get_object_or_404 from rest_framework.exceptions import PermissionDenied -from rest_framework import serializers -from django.conf import settings +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework import status +from rest_framework.request import Request -from applications.models import Application -from authentication.signals import post_auth_failed -from common.utils import get_logger, random_string -from common.mixins.api import SerializerMixin -from common.utils.common import get_file_by_arch -from orgs.mixins.api import RootOrgViewMixin +from common.drf.api import JMSModelViewSet from common.http import is_true +from orgs.mixins.api import RootOrgViewMixin from perms.models.base import Action -from perms.utils.application.permission import get_application_actions -from perms.utils.asset.permission import get_asset_actions -from common.const.http import PATCH from terminal.models import EndpointRule from ..serializers import ( - ConnectionTokenSerializer, ConnectionTokenSecretSerializer, SuperConnectionTokenSerializer + ConnectionTokenSerializer, ConnectionTokenSecretSerializer, SuperConnectionTokenSerializer, + ConnectionTokenDisplaySerializer, ) - -logger = get_logger(__name__) -__all__ = ['UserConnectionTokenViewSet', 'UserSuperConnectionTokenViewSet', 'TokenCacheMixin'] +from ..models import ConnectionToken -class ClientProtocolMixin: - """ - 下载客户端支持的连接文件,里面包含了 token,和 其他连接信息 +__all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet'] - - [x] RDP - - [ ] KoKo - 本质上,这里还是暴露出 token 来,进行使用 - """ +class ConnectionTokenMixin: request: Request - get_serializer: Callable - create_token: Callable - get_serializer_context: Callable + + @staticmethod + def check_token_valid(token: ConnectionToken): + is_valid, error = token.check_valid() + if not is_valid: + raise PermissionDenied(error) + + @staticmethod + def get_request_resources(serializer): + user = serializer.validated_data.get('user') + asset = serializer.validated_data.get('asset') + application = serializer.validated_data.get('application') + system_user = serializer.validated_data.get('system_user') + return user, asset, application, system_user + + @staticmethod + def check_user_has_resource_permission(user, asset, application, system_user): + from perms.utils.asset import has_asset_system_permission + from perms.utils.application import has_application_system_permission + + if asset and not has_asset_system_permission(user, asset, system_user): + error = f'User not has this asset and system user permission: ' \ + f'user={user.id} system_user={system_user.id} asset={asset.id}' + raise PermissionDenied(error) + + if application and not has_application_system_permission(user, application, system_user): + error = f'User not has this application and system user permission: ' \ + f'user={user.id} system_user={system_user.id} application={application.id}' + raise PermissionDenied(error) def get_smart_endpoint(self, protocol, asset=None, application=None): if asset: @@ -64,21 +67,32 @@ class ClientProtocolMixin: endpoint = EndpointRule.match_endpoint(target_ip, protocol, self.request) return endpoint - def get_request_resource(self, serializer): - asset = serializer.validated_data.get('asset') - application = serializer.validated_data.get('application') - system_user = serializer.validated_data['system_user'] - - user = serializer.validated_data.get('user') - user = user if user else self.request.user - return asset, application, system_user, user - @staticmethod def parse_env_bool(env_key, env_default, true_value, false_value): return true_value if is_true(os.getenv(env_key, env_default)) else false_value - def get_rdp_file_content(self, serializer): - options = { + def get_client_protocol_data(self, token: ConnectionToken): + from assets.models import SystemUser + protocol = token.system_user.protocol + username = token.user.username + rdp_config = ssh_token = '' + if protocol == SystemUser.Protocol.rdp: + filename, rdp_config = self.get_rdp_file_info(token) + elif protocol == SystemUser.Protocol.ssh: + filename, ssh_token = self.get_ssh_token(token) + else: + raise ValueError('Protocol not support: {}'.format(protocol)) + + return { + "filename": filename, + "protocol": protocol, + "username": username, + "token": ssh_token, + "config": rdp_config + } + + def get_rdp_file_info(self, token: ConnectionToken): + rdp_options = { 'full address:s': '', 'username:s': '', # 'screen mode id:i': '1', @@ -111,412 +125,164 @@ class ClientProtocolMixin: # 'remoteapplicationcmdline:s': '', } - asset, application, system_user, user = self.get_request_resource(serializer) + # 设置磁盘挂载 + drives_redirect = is_true(self.request.query_params.get('drives_redirect')) + if drives_redirect: + actions = Action.choices_to_value(token.actions) + if actions & Action.UPDOWNLOAD == Action.UPDOWNLOAD: + rdp_options['drivestoredirect:s'] = '*' + + # 设置全屏 + full_screen = is_true(self.request.query_params.get('full_screen')) + rdp_options['screen mode id:i'] = '2' if full_screen else '1' + + # 设置 RDP Server 地址 + endpoint = self.get_smart_endpoint( + protocol='rdp', asset=token.asset, application=token.application + ) + rdp_options['full address:s'] = f'{endpoint.host}:{endpoint.rdp_port}' + + # 设置用户名 + rdp_options['username:s'] = '{}|{}'.format(token.user.username, str(token.id)) + if token.system_user.ad_domain: + rdp_options['domain:s'] = token.system_user.ad_domain + + # 设置宽高 height = self.request.query_params.get('height') width = self.request.query_params.get('width') - full_screen = is_true(self.request.query_params.get('full_screen')) - drives_redirect = is_true(self.request.query_params.get('drives_redirect')) - token, secret = self.create_token(user, asset, application, system_user) - - # 设置磁盘挂载 - if drives_redirect: - actions = 0 - if asset: - actions = get_asset_actions(user, asset, system_user) - elif application: - actions = get_application_actions(user, application, system_user) - - if actions & Action.UPDOWNLOAD == Action.UPDOWNLOAD: - options['drivestoredirect:s'] = '*' - - # 全屏 - options['screen mode id:i'] = '2' if full_screen else '1' - - # RDP Server 地址 - endpoint = self.get_smart_endpoint( - protocol='rdp', asset=asset, application=application - ) - options['full address:s'] = f'{endpoint.host}:{endpoint.rdp_port}' - # 用户名 - options['username:s'] = '{}|{}'.format(user.username, token) - if system_user.ad_domain: - options['domain:s'] = system_user.ad_domain - # 宽高 if width and height: - options['desktopwidth:i'] = width - options['desktopheight:i'] = height - options['winposstr:s:'] = f'0,1,0,0,{width},{height}' + rdp_options['desktopwidth:i'] = width + rdp_options['desktopheight:i'] = height + rdp_options['winposstr:s:'] = f'0,1,0,0,{width},{height}' - options['session bpp:i'] = os.getenv('JUMPSERVER_COLOR_DEPTH', '32') - options['audiomode:i'] = self.parse_env_bool('JUMPSERVER_DISABLE_AUDIO', 'false', '2', '0') + # 设置其他选项 + rdp_options['session bpp:i'] = os.getenv('JUMPSERVER_COLOR_DEPTH', '32') + rdp_options['audiomode:i'] = self.parse_env_bool('JUMPSERVER_DISABLE_AUDIO', 'false', '2', '0') - if asset: - name = asset.hostname - elif application: - name = application.name - application.get_rdp_remote_app_setting() - - app = f'||jmservisor' - options['remoteapplicationmode:i'] = '1' - options['alternate shell:s'] = app - options['remoteapplicationprogram:s'] = app - options['remoteapplicationname:s'] = name + if token.asset: + name = token.asset.hostname + elif token.application and token.application.category_remote_app: + app = '||jmservisor' + name = token.application.name + rdp_options['remoteapplicationmode:i'] = '1' + rdp_options['alternate shell:s'] = app + rdp_options['remoteapplicationprogram:s'] = app + rdp_options['remoteapplicationname:s'] = name else: name = '*' + filename = "{}-{}-jumpserver".format(token.user.username, name) + filename = urllib.parse.quote(filename) + content = '' - for k, v in options.items(): + for k, v in rdp_options.items(): content += f'{k}:{v}\n' - return name, content - def get_ssh_token(self, serializer): - asset, application, system_user, user = self.get_request_resource(serializer) - token, secret = self.create_token(user, asset, application, system_user) - if asset: - name = asset.hostname - elif application: - name = application.name + return filename, content + + def get_ssh_token(self, token: ConnectionToken): + if token.asset: + name = token.asset.hostname + elif token.application: + name = token.application.name else: name = '*' + filename = f'{token.user.username}-{name}-jumpserver' endpoint = self.get_smart_endpoint( - protocol='ssh', asset=asset, application=application + protocol='ssh', asset=token.asset, application=token.application ) - content = { + data = { 'ip': endpoint.host, 'port': str(endpoint.ssh_port), - 'username': f'JMS-{token}', - 'password': secret + 'username': 'JMS-{}'.format(str(token.id)), + 'password': token.secret } - token = json.dumps(content) - return name, token + token = json.dumps(data) + return filename, token - def get_encrypt_cmdline(self, app: Application): - parameters = app.get_rdp_remote_app_setting()['parameters'] - parameters = parameters.encode('ascii') - lib_path = get_file_by_arch('xpack/libs', 'librailencrypt.so') - lib = ctypes.CDLL(lib_path) - lib.encrypt.argtypes = [ctypes.c_char_p, ctypes.c_int] - lib.encrypt.restype = ctypes.c_char_p +class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelViewSet): + filterset_fields = ( + 'type', + 'user_display', 'system_user_display', 'application_display', 'asset_display' + ) + search_fields = filterset_fields + serializer_classes = { + 'default': ConnectionTokenSerializer, + 'list': ConnectionTokenDisplaySerializer, + 'retrieve': ConnectionTokenDisplaySerializer, + 'get_secret_detail': ConnectionTokenSecretSerializer, + } + rbac_perms = { + 'retrieve': 'authentication.view_connectiontoken', + 'create': 'authentication.add_connectiontoken', + 'expire': 'authentication.add_connectiontoken', + 'get_secret_detail': 'authentication.view_connectiontokensecret', + 'get_rdp_file': 'authentication.add_connectiontoken', + 'get_client_protocol_url': 'authentication.add_connectiontoken', + } + queryset = ConnectionToken.objects.all() - rst = lib.encrypt(parameters, len(parameters)) - rst = rst.decode('ascii') - return rst - - def get_valid_serializer(self): - if self.request.method == 'GET': - data = self.request.query_params - else: - data = self.request.data + def create_connection_token(self): + data = self.request.query_params if self.request.method == 'GET' else self.request.data serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) - return serializer + self.perform_create(serializer) + token: ConnectionToken = serializer.instance + return token - def get_client_protocol_data(self, serializer): - asset, application, system_user, user = self.get_request_resource(serializer) - protocol = system_user.protocol - username = user.username - config, token = '', '' - if protocol == 'rdp': - name, config = self.get_rdp_file_content(serializer) - elif protocol == 'ssh': - name, token = self.get_ssh_token(serializer) - else: - raise ValueError('Protocol not support: {}'.format(protocol)) + def perform_create(self, serializer): + user, asset, application, system_user = self.get_request_resources(serializer) + self.check_user_has_resource_permission(user, asset, application, system_user) + return super(ConnectionTokenViewSet, self).perform_create(serializer) - filename = "{}-{}-jumpserver".format(username, name) - data = { - "filename": filename, - "protocol": system_user.protocol, - "username": username, - "token": token, - "config": config - } - return data + @action(methods=['POST'], detail=False, url_path='secret-info/detail') + def get_secret_detail(self, request, *args, **kwargs): + # 非常重要的 api,在逻辑层再判断一下,双重保险 + perm_required = 'authentication.view_connectiontokensecret' + if not request.user.has_perm(perm_required): + raise PermissionDenied('Not allow to view secret') + token_id = request.data.get('token') or '' + token = get_object_or_404(ConnectionToken, pk=token_id) + self.check_token_valid(token) + token.load_system_user_auth() + serializer = self.get_serializer(instance=token) + return Response(serializer.data, status=status.HTTP_200_OK) @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') def get_rdp_file(self, request, *args, **kwargs): - if self.request.method == 'GET': - data = self.request.query_params - else: - data = self.request.data - serializer = self.get_serializer(data=data) - serializer.is_valid(raise_exception=True) - name, data = self.get_rdp_file_content(serializer) - response = HttpResponse(data, content_type='application/octet-stream') - filename = "{}-{}-jumpserver.rdp".format(self.request.user.username, name) - filename = urllib.parse.quote(filename) + token = self.create_connection_token() + self.check_token_valid(token) + filename, content = self.get_rdp_file_info(token) + filename = '{}.rdp'.format(filename) + response = HttpResponse(content, content_type='application/octet-stream') response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % filename return response @action(methods=['POST', 'GET'], detail=False, url_path='client-url') def get_client_protocol_url(self, request, *args, **kwargs): - serializer = self.get_valid_serializer() + token = self.create_connection_token() + self.check_token_valid(token) try: - protocol_data = self.get_client_protocol_data(serializer) + protocol_data = self.get_client_protocol_data(token) except ValueError as e: - return Response({'error': str(e)}, status=401) - + return Response(data={'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) protocol_data = json.dumps(protocol_data).encode() protocol_data = base64.b64encode(protocol_data).decode() data = { - 'url': 'jms://{}'.format(protocol_data), + 'url': 'jms://{}'.format(protocol_data) } return Response(data=data) - -class SecretDetailMixin: - valid_token: Callable - request: Request - get_serializer: Callable - - @staticmethod - def _get_application_secret_detail(application): - gateway = None - remote_app = None - asset = None - - if application.category_remote_app: - remote_app = application.get_rdp_remote_app_setting() - asset = application.get_remote_app_asset() - domain = asset.domain - else: - domain = application.domain - - if domain and domain.has_gateway(): - gateway = domain.random_gateway() - - return { - 'asset': asset, - 'application': application, - 'gateway': gateway, - 'domain': domain, - 'remote_app': remote_app, - } - - @staticmethod - def _get_asset_secret_detail(asset): - gateway = None - if asset and asset.domain and asset.domain.has_gateway(): - gateway = asset.domain.random_gateway() - - return { - 'asset': asset, - 'application': None, - 'domain': asset.domain, - 'gateway': gateway, - 'remote_app': None, - } - - @action(methods=['POST'], detail=False, url_path='secret-info/detail') - def get_secret_detail(self, request, *args, **kwargs): - perm_required = 'authentication.view_connectiontokensecret' - - # 非常重要的 api,再逻辑层再判断一下,双重保险 - if not request.user.has_perm(perm_required): - raise PermissionDenied('Not allow to view secret') - - token = request.data.get('token', '') - try: - value, user, system_user, asset, app, expired_at, actions = 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( - id=token, secret=value.get('secret', ''), - user=user, system_user=system_user, - expired_at=expired_at, actions=actions - ) - cmd_filter_kwargs = { - 'system_user_id': system_user.id, - 'user_id': user.id, - } - if asset: - asset_detail = self._get_asset_secret_detail(asset) - system_user.load_asset_more_auth(asset.id, user.username, user.id) - data['type'] = 'asset' - data.update(asset_detail) - cmd_filter_kwargs['asset_id'] = asset.id - else: - app_detail = self._get_application_secret_detail(app) - system_user.load_app_more_auth(app.id, user.username, user.id) - data['type'] = 'application' - data.update(app_detail) - cmd_filter_kwargs['application_id'] = app.id - - from assets.models import CommandFilterRule - cmd_filter_rules = CommandFilterRule.get_queryset(**cmd_filter_kwargs) - data['cmd_filter_rules'] = cmd_filter_rules - - serializer = self.get_serializer(data) - return Response(data=serializer.data, status=200) + @action(methods=['PATCH'], detail=True) + def expire(self, request, *args, **kwargs): + instance = self.get_object() + instance.expire() + return Response(status=status.HTTP_204_NO_CONTENT) -class TokenCacheMixin: - """ endpoint smart view 用到此类来解析token中的资产、应用 """ - CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}' - - def renewal_token(self, token, ttl=None): - value = self.get_token_from_cache(token) - if value: - pre_ttl = self.get_token_ttl(token) - self.set_token_to_cache(token, value, ttl) - post_ttl = self.get_token_ttl(token) - ok = True - msg = f'{pre_ttl}s is renewed to {post_ttl}s.' - else: - ok = False - msg = 'Token is not found.' - data = { - 'ok': ok, - 'msg': msg - } - return data - - def get_token_ttl(self, token): - key = self.get_token_cache_key(token) - return cache.ttl(key) - - def set_token_to_cache(self, token, value, ttl=None): - key = self.get_token_cache_key(token) - ttl = ttl or settings.CONNECTION_TOKEN_EXPIRATION - cache.set(key, value, timeout=ttl) - - def get_token_from_cache(self, token): - key = self.get_token_cache_key(token) - value = cache.get(key, None) - return value - - def get_token_cache_key(self, token): - return self.CACHE_KEY_PREFIX.format(token) - - -class BaseUserConnectionTokenViewSet( - RootOrgViewMixin, SerializerMixin, ClientProtocolMixin, - TokenCacheMixin, GenericViewSet -): - - @staticmethod - def check_resource_permission(user, asset, application, system_user): - from perms.utils.asset import has_asset_system_permission - from perms.utils.application import has_application_system_permission - - if asset and not has_asset_system_permission(user, asset, system_user): - error = f'User not has this asset and system user permission: ' \ - f'user={user.id} system_user={system_user.id} asset={asset.id}' - raise PermissionDenied(error) - if application and not has_application_system_permission(user, application, system_user): - error = f'User not has this application and system user permission: ' \ - f'user={user.id} system_user={system_user.id} application={application.id}' - raise PermissionDenied(error) - return True - - def create_token(self, user, asset, application, system_user, ttl=None): - self.check_resource_permission(user, asset, application, system_user) - token = random_string(36) - secret = random_string(16) - value = { - 'id': token, - 'secret': secret, - 'user': str(user.id), - 'username': user.username, - 'system_user': str(system_user.id), - 'system_user_name': system_user.name, - 'created_by': str(self.request.user), - 'date_created': str(timezone.now()) - } - - if asset: - value.update({ - 'type': 'asset', - 'asset': str(asset.id), - 'hostname': asset.hostname, - }) - elif application: - value.update({ - 'type': 'application', - 'application': application.id, - 'application_name': str(application) - }) - - self.set_token_to_cache(token, value, ttl) - return token, secret - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - - asset, application, system_user, user = self.get_request_resource(serializer) - token, secret = self.create_token(user, asset, application, system_user) - tp = 'app' if application else 'asset' - data = { - "id": token, 'secret': secret, - 'type': tp, 'protocol': system_user.protocol, - 'expire_time': self.get_token_ttl(token), - } - return Response(data, status=201) - - -class UserConnectionTokenViewSet(BaseUserConnectionTokenViewSet, SecretDetailMixin): - serializer_classes = { - 'default': ConnectionTokenSerializer, - 'get_secret_detail': ConnectionTokenSecretSerializer, - } - rbac_perms = { - 'GET': 'authentication.view_connectiontoken', - 'create': 'authentication.add_connectiontoken', - 'get_secret_detail': 'authentication.view_connectiontokensecret', - 'get_rdp_file': 'authentication.add_connectiontoken', - 'get_client_protocol_url': 'authentication.add_connectiontoken', - } - - def valid_token(self, token): - from users.models import User - from assets.models import SystemUser, Asset - from applications.models import Application - from perms.utils.asset.permission import validate_permission as asset_validate_permission - from perms.utils.application.permission import validate_permission as app_validate_permission - - value = self.get_token_from_cache(token) - if not value: - raise serializers.ValidationError('Token not found') - - user = get_object_or_404(User, id=value.get('user')) - if not user.is_valid: - raise serializers.ValidationError("User not valid, disabled or expired") - - system_user = get_object_or_404(SystemUser, id=value.get('system_user')) - asset = None - app = None - if value.get('type') == 'asset': - asset = get_object_or_404(Asset, id=value.get('asset')) - if not asset.is_active: - raise serializers.ValidationError("Asset disabled") - has_perm, actions, expired_at = asset_validate_permission(user, asset, system_user) - else: - app = get_object_or_404(Application, id=value.get('application')) - has_perm, actions, expired_at = app_validate_permission(user, app, system_user) - - if not has_perm: - raise serializers.ValidationError('Permission expired or invalid') - return value, user, system_user, asset, app, expired_at, actions - - def get(self, request): - token = request.query_params.get('token') - value = self.get_token_from_cache(token) - if not value: - return Response('', status=404) - return Response(value) - - -class UserSuperConnectionTokenViewSet( - BaseUserConnectionTokenViewSet, TokenCacheMixin, GenericViewSet -): +class SuperConnectionTokenViewSet(ConnectionTokenViewSet): serializer_classes = { 'default': SuperConnectionTokenSerializer, } @@ -525,10 +291,19 @@ class UserSuperConnectionTokenViewSet( 'renewal': 'authentication.add_superconnectiontoken' } - @action(methods=[PATCH], detail=False) + @action(methods=['PATCH'], detail=False) def renewal(self, request, *args, **kwargs): - """ 续期 Token """ - token = request.data.get('token', '') - data = self.renewal_token(token) - status_code = 200 if data.get('ok') else 404 - return Response(data=data, status=status_code) + from common.utils.timezone import as_current_tz + + token_id = request.data.get('token') or '' + token = get_object_or_404(ConnectionToken, pk=token_id) + date_expired = as_current_tz(token.date_expired) + if token.is_expired: + raise PermissionDenied('Token is expired at: {}'.format(date_expired)) + token.renewal() + data = { + 'ok': True, + 'msg': f'Token is renewed, date expired: {date_expired}' + } + return Response(data=data, status=status.HTTP_200_OK) + diff --git a/apps/authentication/migrations/0011_auto_20220705_1940.py b/apps/authentication/migrations/0011_auto_20220705_1940.py new file mode 100644 index 000000000..527003d9e --- /dev/null +++ b/apps/authentication/migrations/0011_auto_20220705_1940.py @@ -0,0 +1,89 @@ +# Generated by Django 3.2.12 on 2022-07-05 11:40 + +import authentication.models +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0021_auto_20220629_1826'), + ('assets', '0091_auto_20220629_1826'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('authentication', '0010_temptoken'), + ] + + operations = [ + migrations.AddField( + model_name='connectiontoken', + name='application', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to='applications.application', verbose_name='Application'), + ), + migrations.AddField( + model_name='connectiontoken', + name='application_display', + field=models.CharField(default='', max_length=128, verbose_name='Application display'), + ), + migrations.AddField( + model_name='connectiontoken', + name='asset', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to='assets.asset', verbose_name='Asset'), + ), + migrations.AddField( + model_name='connectiontoken', + name='asset_display', + field=models.CharField(default='', max_length=128, verbose_name='Asset display'), + ), + migrations.AddField( + model_name='connectiontoken', + name='date_expired', + field=models.DateTimeField(default=authentication.models.date_expired_default, verbose_name='Date expired'), + ), + migrations.AddField( + model_name='connectiontoken', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + migrations.AddField( + model_name='connectiontoken', + name='secret', + field=models.CharField(default='', max_length=64, verbose_name='Secret'), + ), + migrations.AddField( + model_name='connectiontoken', + name='system_user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to='assets.systemuser', verbose_name='System user'), + ), + migrations.AddField( + model_name='connectiontoken', + name='system_user_display', + field=models.CharField(default='', max_length=128, verbose_name='System user display'), + ), + migrations.AddField( + model_name='connectiontoken', + name='type', + field=models.CharField(choices=[('asset', 'Asset'), ('application', 'Application')], default='asset', max_length=16, verbose_name='Type'), + ), + migrations.AddField( + model_name='connectiontoken', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + migrations.AddField( + model_name='connectiontoken', + name='user_display', + field=models.CharField(default='', max_length=128, verbose_name='User display'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='id', + field=models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False), + ), + migrations.AlterModelOptions( + name='connectiontoken', + options={'ordering': ('-date_expired',), 'permissions': [('view_connectiontokensecret', 'Can view connection token secret')], 'verbose_name': 'Connection token'}, + ), + ] diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 58e6ccd36..403f7186d 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -443,13 +443,15 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost LoginIpBlockUtil(ip).clean_block_if_need() return user - def mark_password_ok(self, user, auto_login=False): + def mark_password_ok(self, user, auto_login=False, auth_backend=None): request = self.request request.session['auth_password'] = 1 request.session['auth_password_expired_at'] = time.time() + settings.AUTH_EXPIRED_SECONDS request.session['user_id'] = str(user.id) request.session['auto_login'] = auto_login - request.session['auth_backend'] = getattr(user, 'backend', settings.AUTH_BACKEND_MODEL) + if not auth_backend: + auth_backend = getattr(user, 'backend', settings.AUTH_BACKEND_MODEL) + request.session['auth_backend'] = auth_backend def check_oauth2_auth(self, user: User, auth_backend): ip = self.get_request_ip() @@ -469,7 +471,7 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost LoginIpBlockUtil(ip).clean_block_if_need() MFABlockUtils(user.username, ip).clean_failed_count() - self.mark_password_ok(user, False) + self.mark_password_ok(user, False, auth_backend) return user def get_user_or_auth(self, valid_data): diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 54cab4fbd..d04a3fa4e 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -1,11 +1,14 @@ import uuid - +from datetime import datetime, timedelta from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.conf import settings from rest_framework.authtoken.models import Token +from orgs.mixins.models import OrgModelMixin from common.db import models +from common.utils import lazyproperty +from common.utils.timezone import as_current_tz class AccessKey(models.Model): @@ -54,16 +57,197 @@ class SSOToken(models.JMSBaseModel): verbose_name = _('SSO token') -class ConnectionToken(models.JMSBaseModel): - # Todo: 未来可能放到这里,不记录到 redis 了,虽然方便,但是不易于审计 - # Todo: add connection token 可能要授权给 普通用户, 或者放开就行 +def date_expired_default(): + return timezone.now() + timedelta(seconds=settings.CONNECTION_TOKEN_EXPIRATION) + + +class ConnectionToken(OrgModelMixin, models.JMSModel): + class Type(models.TextChoices): + asset = 'asset', _('Asset') + application = 'application', _('Application') + + type = models.CharField( + max_length=16, default=Type.asset, choices=Type.choices, verbose_name=_("Type") + ) + secret = models.CharField(max_length=64, default='', verbose_name=_("Secret")) + date_expired = models.DateTimeField( + default=date_expired_default, verbose_name=_("Date expired") + ) + + user = models.ForeignKey( + 'users.User', on_delete=models.SET_NULL, verbose_name=_('User'), + related_name='connection_tokens', null=True, blank=True + ) + user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) + system_user = models.ForeignKey( + 'assets.SystemUser', on_delete=models.SET_NULL, verbose_name=_('System user'), + related_name='connection_tokens', null=True, blank=True + ) + system_user_display = models.CharField( + max_length=128, default='', verbose_name=_("System user display") + ) + asset = models.ForeignKey( + 'assets.Asset', on_delete=models.SET_NULL, verbose_name=_('Asset'), + related_name='connection_tokens', null=True, blank=True + ) + asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) + application = models.ForeignKey( + 'applications.Application', on_delete=models.SET_NULL, verbose_name=_('Application'), + related_name='connection_tokens', null=True, blank=True + ) + application_display = models.CharField( + max_length=128, default='', verbose_name=_("Application display") + ) class Meta: + ordering = ('-date_expired',) verbose_name = _('Connection token') permissions = [ ('view_connectiontokensecret', _('Can view connection token secret')) ] + @classmethod + def get_default_date_expired(cls): + return date_expired_default() + + @property + def is_expired(self): + return self.date_expired < timezone.now() + + @property + def expire_time(self): + interval = self.date_expired - timezone.now() + seconds = interval.total_seconds() + if seconds < 0: + seconds = 0 + return int(seconds) + + def expire(self): + self.date_expired = timezone.now() + self.save() + + @property + def is_valid(self): + return not self.is_expired + + def is_type(self, tp): + return self.type == tp + + def renewal(self): + """ 续期 Token,将来支持用户自定义创建 token 后,续期策略要修改 """ + self.date_expired = self.get_default_date_expired() + self.save() + + actions = expired_at = None # actions 和 expired_at 在 check_valid() 中赋值 + + def check_valid(self): + from perms.utils.asset.permission import validate_permission as asset_validate_permission + from perms.utils.application.permission import validate_permission as app_validate_permission + + if self.is_expired: + is_valid = False + error = _('Connection token expired at: {}').format(as_current_tz(self.date_expired)) + return is_valid, error + + if not self.user: + is_valid = False + error = _('User not exists') + return is_valid, error + if not self.user.is_valid: + is_valid = False + error = _('User invalid, disabled or expired') + return is_valid, error + + if not self.system_user: + is_valid = False + error = _('System user not exists') + return is_valid, error + + if self.is_type(self.Type.asset): + if not self.asset: + is_valid = False + error = _('Asset not exists') + return is_valid, error + if not self.asset.is_active: + is_valid = False + error = _('Asset inactive') + return is_valid, error + has_perm, actions, expired_at = asset_validate_permission( + self.user, self.asset, self.system_user + ) + if not has_perm: + is_valid = False + error = _('User has no permission to access asset or permission expired') + return is_valid, error + self.actions = actions + self.expired_at = expired_at + + elif self.is_type(self.Type.application): + if not self.application: + is_valid = False + error = _('Application not exists') + return is_valid, error + has_perm, actions, expired_at = app_validate_permission( + self.user, self.application, self.system_user + ) + if not has_perm: + is_valid = False + error = _('User has no permission to access application or permission expired') + return is_valid, error + self.actions = actions + self.expired_at = expired_at + + return True, '' + + @lazyproperty + def domain(self): + if self.asset: + return self.asset.domain + if not self.application: + return + if self.application.category_remote_app: + asset = self.application.get_remote_app_asset() + domain = asset.domain if asset else None + else: + domain = self.application.domain + return domain + + @lazyproperty + def gateway(self): + from assets.models import Domain + if not self.domain: + return + self.domain: Domain + return self.domain.random_gateway() + + @lazyproperty + def remote_app(self): + if not self.application: + return {} + if not self.application.category_remote_app: + return {} + return self.application.get_rdp_remote_app_setting() + + @lazyproperty + def cmd_filter_rules(self): + from assets.models import CommandFilterRule + kwargs = { + 'user_id': self.user.id, + 'system_user_id': self.system_user.id, + } + if self.asset: + kwargs['asset_id'] = self.asset.id + elif self.application: + kwargs['application_id'] = self.application_id + rules = CommandFilterRule.get_queryset(**kwargs) + return rules + + def load_system_user_auth(self): + if self.asset: + self.system_user.load_asset_more_auth(self.asset.id, self.user.username, self.user.id) + elif self.application: + self.system_user.load_app_more_auth(self.application.id, self.user.username, self.user.id) + class TempToken(models.JMSModel): username = models.CharField(max_length=128, verbose_name=_("Username")) diff --git a/apps/authentication/serializers/__init__.py b/apps/authentication/serializers/__init__.py index aa94f0fc8..65994a58c 100644 --- a/apps/authentication/serializers/__init__.py +++ b/apps/authentication/serializers/__init__.py @@ -1,4 +1,4 @@ from .token import * -from .connect_token import * +from .connection_token import * from .password_mfa import * from .confirm import * diff --git a/apps/authentication/serializers/connect_token.py b/apps/authentication/serializers/connect_token.py deleted file mode 100644 index 36fc024b1..000000000 --- a/apps/authentication/serializers/connect_token.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework import serializers - -from users.models import User -from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule -from applications.models import Application -from assets.serializers import ProtocolsField -from perms.serializers.base import ActionsField - -__all__ = [ - 'ConnectionTokenSerializer', 'ConnectionTokenApplicationSerializer', - 'ConnectionTokenUserSerializer', 'ConnectionTokenFilterRuleSerializer', - 'ConnectionTokenAssetSerializer', 'ConnectionTokenSystemUserSerializer', - 'ConnectionTokenDomainSerializer', 'ConnectionTokenRemoteAppSerializer', - 'ConnectionTokenGatewaySerializer', 'ConnectionTokenSecretSerializer', - 'SuperConnectionTokenSerializer' -] - - -class ConnectionTokenSerializer(serializers.Serializer): - system_user = serializers.CharField(max_length=128, required=True) - asset = serializers.CharField(max_length=128, required=False) - application = serializers.CharField(max_length=128, required=False) - - @staticmethod - def validate_system_user(system_user_id): - from assets.models import SystemUser - system_user = SystemUser.objects.filter(id=system_user_id).first() - if system_user is None: - raise serializers.ValidationError('system_user id not exist') - return system_user - - @staticmethod - def validate_asset(asset_id): - from assets.models import Asset - asset = Asset.objects.filter(id=asset_id).first() - if asset is None: - raise serializers.ValidationError('asset id not exist') - return asset - - @staticmethod - def validate_application(app_id): - from applications.models import Application - app = Application.objects.filter(id=app_id).first() - if app is None: - raise serializers.ValidationError('app id not exist') - return app - - def validate(self, attrs): - asset = attrs.get('asset') - application = attrs.get('application') - if not asset and not application: - raise serializers.ValidationError('asset or application required') - if asset and application: - raise serializers.ValidationError('asset and application should only one') - return super().validate(attrs) - - -class SuperConnectionTokenSerializer(ConnectionTokenSerializer): - user = serializers.CharField(max_length=128, required=False, allow_blank=True) - - @staticmethod - def validate_user(user_id): - from users.models import User - user = User.objects.filter(id=user_id).first() - if user is None: - raise serializers.ValidationError('user id not exist') - return user - - -class ConnectionTokenUserSerializer(serializers.ModelSerializer): - class Meta: - model = User - fields = ['id', 'name', 'username', 'email'] - - -class ConnectionTokenAssetSerializer(serializers.ModelSerializer): - protocols = ProtocolsField(label='Protocols', read_only=True) - - class Meta: - model = Asset - fields = ['id', 'hostname', 'ip', 'protocols', 'org_id'] - - -class ConnectionTokenSystemUserSerializer(serializers.ModelSerializer): - class Meta: - model = SystemUser - fields = [ - 'id', 'name', 'username', 'password', 'private_key', - 'protocol', 'ad_domain', 'org_id' - ] - - -class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): - class Meta: - model = Gateway - fields = ['id', 'ip', 'port', 'username', 'password', 'private_key'] - - -class ConnectionTokenRemoteAppSerializer(serializers.Serializer): - program = serializers.CharField() - working_directory = serializers.CharField() - parameters = serializers.CharField() - - -class ConnectionTokenApplicationSerializer(serializers.ModelSerializer): - attrs = serializers.JSONField(read_only=True) - - class Meta: - model = Application - fields = ['id', 'name', 'category', 'type', 'attrs', 'org_id'] - - -class ConnectionTokenDomainSerializer(serializers.ModelSerializer): - gateways = ConnectionTokenGatewaySerializer(many=True, read_only=True) - - class Meta: - model = Domain - fields = ['id', 'name', 'gateways'] - - -class ConnectionTokenFilterRuleSerializer(serializers.ModelSerializer): - class Meta: - model = CommandFilterRule - fields = [ - 'id', 'type', 'content', 'ignore_case', 'pattern', - 'priority', 'action', 'date_created', - ] - - -class ConnectionTokenSecretSerializer(serializers.Serializer): - id = serializers.CharField(read_only=True) - secret = serializers.CharField(read_only=True) - type = serializers.ChoiceField(choices=[('application', 'Application'), ('asset', 'Asset')]) - user = ConnectionTokenUserSerializer(read_only=True) - asset = ConnectionTokenAssetSerializer(read_only=True) - remote_app = ConnectionTokenRemoteAppSerializer(read_only=True) - application = ConnectionTokenApplicationSerializer(read_only=True) - system_user = ConnectionTokenSystemUserSerializer(read_only=True) - cmd_filter_rules = ConnectionTokenFilterRuleSerializer(many=True) - domain = ConnectionTokenDomainSerializer(read_only=True) - gateway = ConnectionTokenGatewaySerializer(read_only=True) - actions = ActionsField() - expired_at = serializers.IntegerField() diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py new file mode 100644 index 000000000..1b639bec6 --- /dev/null +++ b/apps/authentication/serializers/connection_token.py @@ -0,0 +1,195 @@ +from rest_framework import serializers + +from django.utils.translation import ugettext_lazy as _ +from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from authentication.models import ConnectionToken +from common.utils import pretty_string +from common.utils.random import random_string +from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule +from users.models import User +from applications.models import Application +from assets.serializers import ProtocolsField +from perms.serializers.base import ActionsField + + +__all__ = [ + 'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer', + 'SuperConnectionTokenSerializer', 'ConnectionTokenDisplaySerializer' +] + + +class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): + type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) + is_valid = serializers.BooleanField(read_only=True, label=_('Validity')) + expire_time = serializers.IntegerField(read_only=True, label=_('Expired time')) + + class Meta: + model = ConnectionToken + fields_mini = ['id', 'type'] + fields_small = fields_mini + [ + 'secret', 'date_expired', + 'date_created', 'date_updated', 'created_by', 'updated_by', + 'org_id', 'org_name', + ] + fields_fk = [ + 'user', 'system_user', 'asset', 'application', + ] + read_only_fields = [ + # 普通 Token 不支持指定 user + 'user', 'is_valid', 'expire_time', + 'type_display', 'user_display', 'system_user_display', 'asset_display', + 'application_display', + ] + fields = fields_small + fields_fk + read_only_fields + + def validate(self, attrs): + fields_attrs = self.construct_internal_fields_attrs(attrs) + attrs.update(fields_attrs) + return attrs + + @property + def request_user(self): + request = self.context.get('request') + if request: + return request.user + + def get_user(self, attrs): + return self.request_user + + def construct_internal_fields_attrs(self, attrs): + user = self.get_user(attrs) + system_user = attrs.get('system_user') or '' + asset = attrs.get('asset') or '' + application = attrs.get('application') or '' + secret = attrs.get('secret') or random_string(64) + date_expired = attrs.get('date_expired') or ConnectionToken.get_default_date_expired() + + if isinstance(asset, Asset): + tp = ConnectionToken.Type.asset + org_id = asset.org_id + elif isinstance(application, Application): + tp = ConnectionToken.Type.application + org_id = application.org_id + else: + raise serializers.ValidationError(_('Asset or application required')) + + return { + 'type': tp, + 'user': user, + 'secret': secret, + 'date_expired': date_expired, + 'user_display': pretty_string(str(user), max_length=128), + 'system_user_display': pretty_string(str(system_user), max_length=128), + 'asset_display': pretty_string(str(asset), max_length=128), + 'application_display': pretty_string(str(application), max_length=128), + 'org_id': org_id, + } + + +class ConnectionTokenDisplaySerializer(ConnectionTokenSerializer): + class Meta(ConnectionTokenSerializer.Meta): + extra_kwargs = { + 'secret': {'write_only': True}, + } + + +# +# SuperConnectionTokenSerializer +# + + +class SuperConnectionTokenSerializer(ConnectionTokenSerializer): + + class Meta(ConnectionTokenSerializer.Meta): + read_only_fields = [ + 'validity', + 'user_display', 'system_user_display', 'asset_display', 'application_display', + ] + + def get_user(self, attrs): + return attrs.get('user') or self.request_user + + +# +# Connection Token Secret +# + + +class ConnectionTokenUserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['id', 'name', 'username', 'email'] + + +class ConnectionTokenAssetSerializer(serializers.ModelSerializer): + protocols = ProtocolsField(label='Protocols', read_only=True) + + class Meta: + model = Asset + fields = ['id', 'hostname', 'ip', 'protocols', 'org_id'] + + +class ConnectionTokenSystemUserSerializer(serializers.ModelSerializer): + class Meta: + model = SystemUser + fields = [ + 'id', 'name', 'username', 'password', 'private_key', + 'protocol', 'ad_domain', 'org_id' + ] + + +class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): + class Meta: + model = Gateway + fields = ['id', 'ip', 'port', 'username', 'password', 'private_key'] + + +class ConnectionTokenRemoteAppSerializer(serializers.Serializer): + program = serializers.CharField(allow_null=True, allow_blank=True) + working_directory = serializers.CharField(allow_null=True, allow_blank=True) + parameters = serializers.CharField(allow_null=True, allow_blank=True) + + +class ConnectionTokenApplicationSerializer(serializers.ModelSerializer): + attrs = serializers.JSONField(read_only=True) + + class Meta: + model = Application + fields = ['id', 'name', 'category', 'type', 'attrs', 'org_id'] + + +class ConnectionTokenDomainSerializer(serializers.ModelSerializer): + gateways = ConnectionTokenGatewaySerializer(many=True, read_only=True) + + class Meta: + model = Domain + fields = ['id', 'name', 'gateways'] + + +class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): + class Meta: + model = CommandFilterRule + fields = [ + 'id', 'type', 'content', 'ignore_case', 'pattern', + 'priority', 'action', 'date_created', + ] + + +class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): + user = ConnectionTokenUserSerializer(read_only=True) + asset = ConnectionTokenAssetSerializer(read_only=True) + application = ConnectionTokenApplicationSerializer(read_only=True) + remote_app = ConnectionTokenRemoteAppSerializer(read_only=True) + system_user = ConnectionTokenSystemUserSerializer(read_only=True) + gateway = ConnectionTokenGatewaySerializer(read_only=True) + domain = ConnectionTokenDomainSerializer(read_only=True) + cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) + actions = ActionsField() + expired_at = serializers.IntegerField() + + class Meta: + model = ConnectionToken + fields = [ + 'id', 'secret', 'type', 'user', 'asset', 'application', 'system_user', + 'remote_app', 'cmd_filter_rules', 'domain', 'gateway', 'actions', 'expired_at', + ] diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index 02c920c5b..cfbac879f 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -10,8 +10,8 @@ router = DefaultRouter() router.register('access-keys', api.AccessKeyViewSet, 'access-key') router.register('sso', api.SSOViewSet, 'sso') router.register('temp-tokens', api.TempTokenViewSet, 'temp-token') -router.register('connection-token', api.UserConnectionTokenViewSet, 'connection-token') -router.register('super-connection-token', api.UserSuperConnectionTokenViewSet, 'super-connection-token') +router.register('connection-token', api.ConnectionTokenViewSet, 'connection-token') +router.register('super-connection-token', api.SuperConnectionTokenViewSet, 'super-connection-token') urlpatterns = [ @@ -27,6 +27,7 @@ urlpatterns = [ path('auth/', api.TokenCreateApi.as_view(), name='user-auth'), path('confirm/', api.ConfirmApi.as_view(), name='user-confirm'), + path('confirm-oauth/', api.ConfirmBindORUNBindOAuth.as_view(), name='confirm-oauth'), path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'), path('mfa/verify/', api.MFAChallengeVerifyApi.as_view(), name='mfa-verify'), path('mfa/challenge/', api.MFAChallengeVerifyApi.as_view(), name='mfa-challenge'), diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py index c9315bdb5..b896aa19e 100644 --- a/apps/authentication/views/wecom.py +++ b/apps/authentication/views/wecom.py @@ -300,5 +300,4 @@ class WeComOAuthLoginCallbackView(AuthMixin, WeComOAuthMixin, View): msg = e.msg response = self.get_failed_response(login_url, title=msg, msg=msg) return response - return self.redirect_to_guard_view() diff --git a/apps/common/db/encoder.py b/apps/common/db/encoder.py index 673b8aeca..e09ffd60e 100644 --- a/apps/common/db/encoder.py +++ b/apps/common/db/encoder.py @@ -4,6 +4,7 @@ import logging from datetime import datetime from django.utils.translation import ugettext_lazy as _ +from django.utils import timezone as dj_timezone from django.db import models from django.conf import settings @@ -18,6 +19,7 @@ class ModelJSONFieldEncoder(json.JSONEncoder): if isinstance(obj, str_cls): return str(obj) elif isinstance(obj, datetime): + obj = dj_timezone.localtime(obj) return obj.strftime(settings.DATETIME_DISPLAY_FORMAT) elif isinstance(obj, (list, tuple)) and len(obj) > 0 \ and isinstance(obj[0], models.Model): diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index c3a0a0210..551076192 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -383,6 +383,7 @@ class Config(dict): 'SESSION_COOKIE_SECURE': False, 'CSRF_COOKIE_SECURE': False, 'REFERER_CHECK_ENABLED': False, + 'SESSION_ENGINE': 'cache', 'SESSION_SAVE_EVERY_REQUEST': True, 'SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE': False, 'SERVER_REPLAY_STORAGE': {}, diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 9b4cfb668..519623ac9 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -3,6 +3,7 @@ import platform if platform.system() == 'Darwin' and platform.machine() == 'arm64': import pymysql + pymysql.version_info = (1, 4, 2, "final", 0) pymysql.install_as_MySQLdb() @@ -109,8 +110,6 @@ MIDDLEWARE = [ 'simple_history.middleware.HistoryRequestMiddleware', ] - - ROOT_URLCONF = 'jumpserver.urls' TEMPLATES = [ @@ -161,7 +160,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 自定义的配置,SESSION_EXPIRE_AT_BROWSER_CLOSE 始终为 True, 下面这个来控制是否强制关闭后过期 cookie SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE = CONFIG.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE SESSION_SAVE_EVERY_REQUEST = CONFIG.SESSION_SAVE_EVERY_REQUEST -SESSION_ENGINE = "django.contrib.sessions.backends.cache" +SESSION_ENGINE = "django.contrib.sessions.backends.{}".format(CONFIG.SESSION_ENGINE) MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' # Database diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index b1be6955d..6d3f88208 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba75ec2955d452a16930dbd845c8091de4d3cbe0c5b5ccb18f8deb28d7bf3809 -size 126865 +oid sha256:326eef1f3134c1500a6641c6616a9d509befd5db42ead551fe5ca01b3e0273c0 +size 128150 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 8668dd892..9205fb974 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-07-06 16:47+0800\n" +"POT-Creation-Date: 2022-07-12 17:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -47,7 +47,7 @@ msgstr "優先順位" msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" -#: acls/models/base.py:31 authentication/models.py:18 +#: acls/models/base.py:31 authentication/models.py:21 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:88 terminal/models/sharing.py:28 tickets/const.py:39 msgid "Active" @@ -58,12 +58,12 @@ msgstr "アクティブ" #: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 #: assets/models/cmd_filter.py:96 assets/models/domain.py:24 -#: assets/models/domain.py:64 assets/models/group.py:23 +#: assets/models/domain.py:65 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/endpoint.py:21 terminal/models/endpoint.py:92 #: terminal/models/storage.py:29 terminal/models/terminal.py:114 -#: tickets/models/comment.py:32 tickets/models/ticket/general.py:278 +#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 @@ -88,8 +88,8 @@ msgstr "ログイン確認" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:62 audits/models.py:87 audits/serializers.py:100 -#: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:118 rbac/models/rolebinding.py:41 +#: authentication/models.py:54 authentication/models.py:78 orgs/models.py:214 +#: perms/models/base.py:84 rbac/builtin.py:118 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 #: terminal/models/sharing.py:33 terminal/notifications.py:91 @@ -131,6 +131,7 @@ msgstr "システムユーザー" #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 #: assets/serializers/system_user.py:268 audits/models.py:39 +#: authentication/models.py:66 authentication/models.py:90 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 #: terminal/notifications.py:90 @@ -156,7 +157,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: assets/models/gathered_user.py:15 audits/models.py:121 #: authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models.py:69 +#: authentication/models.py:245 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:659 @@ -179,7 +180,7 @@ msgstr "" #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 #: applications/serializers/attrs/application_type/mysql_workbench.py:18 -#: assets/models/asset.py:210 assets/models/domain.py:60 +#: assets/models/asset.py:210 assets/models/domain.py:61 #: assets/serializers/account.py:13 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 @@ -203,7 +204,7 @@ msgstr "" "ション: {}" #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 -#: assets/models/domain.py:62 assets/models/user.py:252 +#: assets/models/domain.py:63 assets/models/user.py:252 #: terminal/serializers/session.py:31 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "プロトコル" @@ -261,13 +262,14 @@ msgstr "カスタム" #: applications/models/account.py:12 applications/models/application.py:234 #: assets/models/backup.py:32 assets/models/cmd_filter.py:45 +#: authentication/models.py:67 authentication/models.py:95 #: perms/models/application_permission.py:28 msgid "Application" msgstr "アプリケーション" #: applications/models/account.py:15 assets/models/authbook.py:20 #: assets/models/cmd_filter.py:42 assets/models/user.py:342 audits/models.py:40 -#: perms/models/application_permission.py:33 +#: authentication/models.py:83 perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:36 terminal/models/session.py:48 #: xpack/plugins/change_auth_plan/models/app.py:36 @@ -305,19 +307,19 @@ msgstr "カテゴリ" #: applications/models/application.py:222 #: applications/serializers/application.py:101 assets/models/backup.py:49 #: assets/models/cmd_filter.py:82 assets/models/user.py:250 -#: perms/models/application_permission.py:24 +#: authentication/models.py:70 perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 -#: terminal/models/storage.py:58 terminal/models/storage.py:142 +#: terminal/models/storage.py:58 terminal/models/storage.py:143 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 -#: tickets/models/ticket/general.py:263 +#: tickets/models/ticket/general.py:273 #: xpack/plugins/change_auth_plan/models/app.py:28 #: xpack/plugins/change_auth_plan/models/app.py:153 msgid "Type" msgstr "タイプ" #: applications/models/application.py:226 assets/models/asset.py:217 -#: assets/models/domain.py:29 assets/models/domain.py:63 +#: assets/models/domain.py:29 assets/models/domain.py:64 msgid "Domain" msgstr "ドメイン" @@ -343,7 +345,8 @@ msgstr "カテゴリ表示" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 #: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:34 -#: audits/serializers.py:29 perms/serializers/application/permission.py:19 +#: audits/serializers.py:29 authentication/serializers/connection_token.py:22 +#: perms/serializers/application/permission.py:19 #: tickets/serializers/flow.py:48 tickets/serializers/ticket/ticket.py:17 msgid "Type display" msgstr "タイプ表示" @@ -370,7 +373,7 @@ msgid "Date updated" msgstr "更新日" #: applications/serializers/application.py:121 -#: applications/serializers/application.py:166 +#: applications/serializers/application.py:166 authentication/models.py:99 msgid "Application display" msgstr "アプリケーション表示" @@ -398,7 +401,7 @@ msgstr "ホスト" #: applications/serializers/attrs/application_type/pgsql.py:10 #: applications/serializers/attrs/application_type/redis.py:10 #: applications/serializers/attrs/application_type/sqlserver.py:10 -#: assets/models/asset.py:214 assets/models/domain.py:61 +#: assets/models/asset.py:214 assets/models/domain.py:62 #: settings/serializers/auth/radius.py:15 #: xpack/plugins/cloud/serializers/account_attrs.py:71 msgid "Port" @@ -413,6 +416,8 @@ msgstr "アプリケーションパス" #: applications/serializers/attrs/application_category/remote_app.py:44 #: assets/serializers/system_user.py:167 +#: tickets/serializers/ticket/apply_application.py:35 +#: tickets/serializers/ticket/common.py:59 #: xpack/plugins/change_auth_plan/serializers/asset.py:67 #: xpack/plugins/change_auth_plan/serializers/asset.py:70 #: xpack/plugins/change_auth_plan/serializers/asset.py:73 @@ -500,7 +505,7 @@ msgid "Charset" msgstr "シャーセット" #: assets/models/asset.py:141 assets/serializers/asset.py:176 -#: tickets/models/ticket/general.py:288 +#: tickets/models/ticket/general.py:298 msgid "Meta" msgstr "メタ" @@ -522,7 +527,7 @@ msgstr "ベンダー" msgid "Model" msgstr "モデル" -#: assets/models/asset.py:170 tickets/models/ticket/general.py:286 +#: assets/models/asset.py:170 tickets/models/ticket/general.py:296 msgid "Serial number" msgstr "シリアル番号" @@ -572,7 +577,7 @@ msgstr "ホスト名生" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:43 msgid "Protocols" msgstr "プロトコル" @@ -584,8 +589,8 @@ msgid "Nodes" msgstr "ノード" #: assets/models/asset.py:219 assets/models/cmd_filter.py:47 -#: assets/models/domain.py:65 assets/models/label.py:22 -#: users/serializers/user.py:145 +#: assets/models/domain.py:66 assets/models/label.py:22 +#: users/serializers/user.py:147 msgid "Is active" msgstr "アクティブです。" @@ -763,7 +768,7 @@ msgstr "失敗しました" msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:40 authentication/models.py:72 +#: assets/models/base.py:40 authentication/models.py:248 msgid "Date verified" msgstr "確認済みの日付" @@ -773,7 +778,7 @@ msgstr "確認済みの日付" #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 -#: users/forms/profile.py:22 users/serializers/user.py:92 +#: users/forms/profile.py:22 users/serializers/user.py:94 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -897,24 +902,24 @@ msgstr "生成された正規表現が正しくありません: {}" msgid "Command confirm" msgstr "コマンドの確認" -#: assets/models/domain.py:72 +#: assets/models/domain.py:73 msgid "Gateway" msgstr "ゲートウェイ" -#: assets/models/domain.py:74 +#: assets/models/domain.py:75 msgid "Test gateway" msgstr "テストゲートウェイ" -#: assets/models/domain.py:130 +#: assets/models/domain.py:131 #, python-brace-format msgid "Unable to connect to port {port} on {ip}" msgstr "{ip} でポート {port} に接続できません" -#: assets/models/domain.py:133 xpack/plugins/cloud/providers/fc.py:48 +#: assets/models/domain.py:134 xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "認証に失敗しました" -#: assets/models/domain.py:135 assets/models/domain.py:157 +#: assets/models/domain.py:136 assets/models/domain.py:158 msgid "Connect failed" msgstr "接続に失敗しました" @@ -971,7 +976,7 @@ msgid "Parent key" msgstr "親キー" #: assets/models/node.py:559 assets/serializers/system_user.py:267 -#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:70 msgid "Node" msgstr "ノード" @@ -1030,7 +1035,7 @@ msgid "SFTP Root" msgstr "SFTPルート" #: assets/models/user.py:258 assets/serializers/system_user.py:37 -#: authentication/models.py:49 +#: authentication/models.py:52 msgid "Token" msgstr "トークン" @@ -1082,7 +1087,7 @@ msgstr "" "定してください暗号化パスワード" #: assets/serializers/account.py:36 assets/serializers/account.py:87 -#: assets/serializers/account_history.py:10 +#: assets/serializers/account_history.py:10 authentication/models.py:87 msgid "System user display" msgstr "システムユーザー表示" @@ -1522,7 +1527,7 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:128 terminal/models/status.py:33 -#: tickets/models/ticket/general.py:271 xpack/plugins/cloud/models.py:175 +#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:175 #: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "ステータス" @@ -1576,7 +1581,8 @@ msgstr "として実行" msgid "Run as display" msgstr "ディスプレイとして実行する" -#: audits/serializers.py:102 rbac/serializers/rolebinding.py:21 +#: audits/serializers.py:102 authentication/models.py:81 +#: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "ユーザー表示" @@ -1598,192 +1604,194 @@ msgstr "認証トークン" msgid "WeCom" msgstr "企業微信" -#: audits/signal_handlers.py:54 authentication/views/dingtalk.py:179 +#: audits/signal_handlers.py:54 authentication/views/feishu.py:144 +#: authentication/views/login.py:79 notifications/backends/__init__.py:14 +#: users/models/user.py:722 +msgid "FeiShu" +msgstr "本を飛ばす" + +#: audits/signal_handlers.py:55 authentication/views/dingtalk.py:179 #: authentication/views/login.py:73 notifications/backends/__init__.py:12 #: users/models/user.py:721 msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:55 authentication/models.py:76 +#: audits/signal_handlers.py:56 authentication/models.py:252 msgid "Temporary token" msgstr "仮パスワード" -#: audits/signal_handlers.py:67 +#: audits/signal_handlers.py:68 msgid "User and Group" msgstr "ユーザーとグループ" -#: audits/signal_handlers.py:68 +#: audits/signal_handlers.py:69 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} に参加 {UserGroup}" -#: audits/signal_handlers.py:69 +#: audits/signal_handlers.py:70 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} のそばを通る {UserGroup}" -#: audits/signal_handlers.py:72 +#: audits/signal_handlers.py:73 msgid "Asset and SystemUser" msgstr "資産およびシステム・ユーザー" -#: audits/signal_handlers.py:73 +#: audits/signal_handlers.py:74 #, python-brace-format msgid "{Asset} ADD {SystemUser}" msgstr "{Asset} 追加 {SystemUser}" -#: audits/signal_handlers.py:74 +#: audits/signal_handlers.py:75 #, python-brace-format msgid "{Asset} REMOVE {SystemUser}" msgstr "{Asset} 削除 {SystemUser}" -#: audits/signal_handlers.py:77 +#: audits/signal_handlers.py:78 msgid "Node and Asset" msgstr "ノードと資産" -#: audits/signal_handlers.py:78 +#: audits/signal_handlers.py:79 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 追加 {Asset}" -#: audits/signal_handlers.py:79 +#: audits/signal_handlers.py:80 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 削除 {Asset}" -#: audits/signal_handlers.py:82 +#: audits/signal_handlers.py:83 msgid "User asset permissions" msgstr "ユーザー資産の権限" -#: audits/signal_handlers.py:83 +#: audits/signal_handlers.py:84 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 追加 {User}" -#: audits/signal_handlers.py:84 +#: audits/signal_handlers.py:85 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 削除 {User}" -#: audits/signal_handlers.py:87 +#: audits/signal_handlers.py:88 msgid "User group asset permissions" msgstr "ユーザーグループの資産権限" -#: audits/signal_handlers.py:88 +#: audits/signal_handlers.py:89 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:89 +#: audits/signal_handlers.py:90 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:92 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:93 perms/models/asset_permission.py:29 msgid "Asset permission" msgstr "資産権限" -#: audits/signal_handlers.py:93 +#: audits/signal_handlers.py:94 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 追加 {Asset}" -#: audits/signal_handlers.py:94 +#: audits/signal_handlers.py:95 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 削除 {Asset}" -#: audits/signal_handlers.py:97 +#: audits/signal_handlers.py:98 msgid "Node permission" msgstr "ノード権限" -#: audits/signal_handlers.py:98 +#: audits/signal_handlers.py:99 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 追加 {Node}" -#: audits/signal_handlers.py:99 +#: audits/signal_handlers.py:100 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 削除 {Node}" -#: audits/signal_handlers.py:102 +#: audits/signal_handlers.py:103 msgid "Asset permission and SystemUser" msgstr "資産権限とSystemUser" -#: audits/signal_handlers.py:103 +#: audits/signal_handlers.py:104 #, python-brace-format msgid "{AssetPermission} ADD {SystemUser}" msgstr "{AssetPermission} 追加 {SystemUser}" -#: audits/signal_handlers.py:104 +#: audits/signal_handlers.py:105 #, python-brace-format msgid "{AssetPermission} REMOVE {SystemUser}" msgstr "{AssetPermission} 削除 {SystemUser}" -#: audits/signal_handlers.py:107 +#: audits/signal_handlers.py:108 msgid "User application permissions" msgstr "ユーザーアプリケーションの権限" -#: audits/signal_handlers.py:108 +#: audits/signal_handlers.py:109 #, python-brace-format msgid "{ApplicationPermission} ADD {User}" msgstr "{ApplicationPermission} 追加 {User}" -#: audits/signal_handlers.py:109 +#: audits/signal_handlers.py:110 #, python-brace-format msgid "{ApplicationPermission} REMOVE {User}" msgstr "{ApplicationPermission} 削除 {User}" -#: audits/signal_handlers.py:112 +#: audits/signal_handlers.py:113 msgid "User group application permissions" msgstr "ユーザーグループアプリケーションの権限" -#: audits/signal_handlers.py:113 +#: audits/signal_handlers.py:114 #, python-brace-format msgid "{ApplicationPermission} ADD {UserGroup}" msgstr "{ApplicationPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:114 +#: audits/signal_handlers.py:115 #, python-brace-format msgid "{ApplicationPermission} REMOVE {UserGroup}" msgstr "{ApplicationPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:117 perms/models/application_permission.py:38 +#: audits/signal_handlers.py:118 perms/models/application_permission.py:38 msgid "Application permission" msgstr "申請許可" -#: audits/signal_handlers.py:118 +#: audits/signal_handlers.py:119 #, python-brace-format msgid "{ApplicationPermission} ADD {Application}" msgstr "{ApplicationPermission} 追加 {Application}" -#: audits/signal_handlers.py:119 +#: audits/signal_handlers.py:120 #, python-brace-format msgid "{ApplicationPermission} REMOVE {Application}" msgstr "{ApplicationPermission} 削除 {Application}" -#: audits/signal_handlers.py:122 +#: audits/signal_handlers.py:123 msgid "Application permission and SystemUser" msgstr "アプリケーション権限とSystemUser" -#: audits/signal_handlers.py:123 +#: audits/signal_handlers.py:124 #, python-brace-format msgid "{ApplicationPermission} ADD {SystemUser}" msgstr "{ApplicationPermission} 追加 {SystemUser}" -#: audits/signal_handlers.py:124 +#: audits/signal_handlers.py:125 #, python-brace-format msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 削除 {SystemUser}" -#: authentication/api/confirm.py:33 +#: authentication/api/confirm.py:40 msgid "This action require verify your MFA" msgstr "この操作には、MFAを検証する必要があります" -#: authentication/api/connection_token.py:326 -msgid "Invalid token" -msgstr "無効なトークン" - #: authentication/api/mfa.py:59 msgid "Current user not support mfa type: {}" msgstr "現在のユーザーはmfaタイプをサポートしていません: {}" @@ -2082,47 +2090,91 @@ msgstr "MFAタイプ ({}) が有効になっていない" msgid "Please change your password" msgstr "パスワードを変更してください" -#: authentication/models.py:34 +#: authentication/models.py:37 msgid "Access key" msgstr "アクセスキー" -#: authentication/models.py:41 +#: authentication/models.py:44 msgid "Private Token" msgstr "プライベートトークン" -#: authentication/models.py:50 +#: authentication/models.py:53 msgid "Expired" msgstr "期限切れ" -#: authentication/models.py:54 +#: authentication/models.py:57 msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:62 -msgid "Connection token" -msgstr "接続トークン" - -#: authentication/models.py:64 -msgid "Can view connection token secret" -msgstr "接続トークンの秘密を表示できます" - -#: authentication/models.py:70 +#: authentication/models.py:72 authentication/models.py:246 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:17 msgid "Secret" msgstr "ひみつ" -#: authentication/models.py:71 -msgid "Verified" -msgstr "確認済み" - -#: authentication/models.py:73 perms/models/base.py:90 -#: tickets/models/ticket/apply_application.py:26 +#: authentication/models.py:74 authentication/models.py:249 +#: perms/models/base.py:90 tickets/models/ticket/apply_application.py:26 #: tickets/models/ticket/apply_asset.py:24 users/models/user.py:703 msgid "Date expired" msgstr "期限切れの日付" -#: authentication/models.py:92 +#: authentication/models.py:93 +msgid "Asset display" +msgstr "アセット名" + +#: authentication/models.py:104 +msgid "Connection token" +msgstr "接続トークン" + +#: authentication/models.py:106 +msgid "Can view connection token secret" +msgstr "接続トークンの秘密を表示できます" + +#: authentication/models.py:141 +msgid "Connection token expired at: {}" +msgstr "接続トークンの有効期限: {}" + +#: authentication/models.py:146 +msgid "User not exists" +msgstr "ユーザーは存在しません" + +#: authentication/models.py:150 +msgid "User invalid, disabled or expired" +msgstr "ユーザーが無効、無効、または期限切れです" + +#: authentication/models.py:155 +msgid "System user not exists" +msgstr "システムユーザーが存在しません" + +#: authentication/models.py:161 +msgid "Asset not exists" +msgstr "アセットが存在しません" + +#: authentication/models.py:165 +msgid "Asset inactive" +msgstr "アセットがアクティブ化されていません" + +#: authentication/models.py:172 +msgid "User has no permission to access asset or permission expired" +msgstr "" +"ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れて" +"います" + +#: authentication/models.py:180 +msgid "Application not exists" +msgstr "アプリが存在しません" + +#: authentication/models.py:187 +msgid "User has no permission to access application or permission expired" +msgstr "" +"ユーザーがアプリにアクセスする権限を持っていないか、権限の有効期限が切れてい" +"ます" + +#: authentication/models.py:247 +msgid "Verified" +msgstr "確認済み" + +#: authentication/models.py:268 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2134,11 +2186,20 @@ msgstr "異なる都市ログインのリマインダー" msgid "binding reminder" msgstr "バインディングリマインダー" +#: authentication/serializers/connection_token.py:23 +#: xpack/plugins/cloud/models.py:34 +msgid "Validity" +msgstr "有効性" + +#: authentication/serializers/connection_token.py:73 +msgid "Asset or application required" +msgstr "アセットまたはアプリが必要" + #: authentication/serializers/token.py:79 #: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:41 #: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:146 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:148 msgid "Is valid" msgstr "有効です" @@ -2429,11 +2490,6 @@ msgstr "FeiShuクエリユーザーが失敗しました" msgid "The FeiShu is already bound to another user" msgstr "FeiShuはすでに別のユーザーにバインドされています" -#: authentication/views/feishu.py:144 authentication/views/login.py:79 -#: notifications/backends/__init__.py:14 users/models/user.py:722 -msgid "FeiShu" -msgstr "本を飛ばす" - #: authentication/views/feishu.py:145 msgid "Binding FeiShu successfully" msgstr "本を飛ばすのバインドに成功" @@ -2528,7 +2584,7 @@ msgstr "%(name)s が正常に作成されました" msgid "%(name)s was updated successfully" msgstr "%(name)s は正常に更新されました" -#: common/db/encoder.py:10 +#: common/db/encoder.py:11 msgid "ugettext_lazy" msgstr "ugettext_lazy" @@ -2956,7 +3012,7 @@ msgstr "アプリ組織" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 #: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:290 tickets/serializers/ticket/ticket.py:64 +#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:64 msgid "Organization" msgstr "組織" @@ -3092,8 +3148,8 @@ msgstr "Organization {} のアプリケーション権限" #: perms/serializers/application/permission.py:21 #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 -#: perms/serializers/asset/permission.py:44 users/serializers/user.py:87 -#: users/serializers/user.py:148 +#: perms/serializers/asset/permission.py:44 users/serializers/user.py:89 +#: users/serializers/user.py:150 msgid "Is expired" msgstr "期限切れです" @@ -3364,7 +3420,7 @@ msgstr "マイアプリ" msgid "Ticket comment" msgstr "チケットコメント" -#: rbac/tree.py:115 tickets/models/ticket/general.py:295 +#: rbac/tree.py:115 tickets/models/ticket/general.py:305 msgid "Ticket" msgstr "チケット" @@ -4618,6 +4674,10 @@ msgstr "" "Jmservisorはwindowsリモートアプリケーションパブリケーションサーバでリモートア" "プリケーションを引き出すためのプログラムです" +#: templates/resource_download.html:51 +msgid "Offline video player" +msgstr "オフラインビデオプレーヤー" + #: terminal/api/endpoint.py:26 msgid "Not found protocol query params" msgstr "プロトコルクエリパラメータが見つかりません" @@ -4882,7 +4942,7 @@ msgstr "リンク期限切れ" msgid "User not allowed to join" msgstr "IPは許可されていません" -#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:58 +#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:59 msgid "Joiner" msgstr "ジョイナー" @@ -4939,11 +4999,11 @@ msgstr "ブート時間" msgid "Default storage" msgstr "デフォルトのストレージ" -#: terminal/models/storage.py:136 terminal/models/terminal.py:108 +#: terminal/models/storage.py:137 terminal/models/terminal.py:108 msgid "Command storage" msgstr "コマンドストレージ" -#: terminal/models/storage.py:196 terminal/models/terminal.py:109 +#: terminal/models/storage.py:197 terminal/models/terminal.py:109 msgid "Replay storage" msgstr "再生ストレージ" @@ -5196,7 +5256,19 @@ msgstr "" "チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: " "{}" -#: tickets/handlers/base.py:72 +#: tickets/handlers/base.py:79 +msgid "Change field" +msgstr "フィールドを変更" + +#: tickets/handlers/base.py:79 +msgid "Before change" +msgstr "変更前" + +#: tickets/handlers/base.py:79 +msgid "After change" +msgstr "変更後" + +#: tickets/handlers/base.py:91 msgid "{} {} the ticket" msgstr "{} {} チケット" @@ -5212,8 +5284,8 @@ msgstr "応用ログイン都市" msgid "Applied login datetime" msgstr "適用されたログインの日付時間" -#: tickets/models/comment.py:13 tickets/models/ticket/general.py:39 -#: tickets/models/ticket/general.py:267 +#: tickets/models/comment.py:13 tickets/models/ticket/general.py:41 +#: tickets/models/ticket/general.py:277 msgid "State" msgstr "状態" @@ -5230,7 +5302,7 @@ msgid "Body" msgstr "ボディ" #: tickets/models/flow.py:20 tickets/models/flow.py:62 -#: tickets/models/ticket/general.py:35 +#: tickets/models/ticket/general.py:37 msgid "Approve level" msgstr "レベルを承認する" @@ -5256,8 +5328,8 @@ msgstr "チケットセッションの関係" #: tickets/models/ticket/apply_application.py:11 #: tickets/models/ticket/apply_asset.py:13 -msgid "Apply name" -msgstr "名前を適用" +msgid "Permission name" +msgstr "認可ルール名" #: tickets/models/ticket/apply_application.py:20 msgid "Apply applications" @@ -5305,39 +5377,39 @@ msgstr "コマンドフィルタ規則から" msgid "From cmd filter rule" msgstr "コマンドフィルタ規則から" -#: tickets/models/ticket/general.py:70 +#: tickets/models/ticket/general.py:72 msgid "Ticket step" msgstr "チケットステップ" -#: tickets/models/ticket/general.py:88 +#: tickets/models/ticket/general.py:90 msgid "Ticket assignee" msgstr "割り当てられたチケット" -#: tickets/models/ticket/general.py:260 +#: tickets/models/ticket/general.py:270 msgid "Title" msgstr "タイトル" -#: tickets/models/ticket/general.py:276 +#: tickets/models/ticket/general.py:286 msgid "Applicant" msgstr "応募者" -#: tickets/models/ticket/general.py:281 +#: tickets/models/ticket/general.py:291 msgid "TicketFlow" msgstr "作業指示プロセス" -#: tickets/models/ticket/general.py:284 +#: tickets/models/ticket/general.py:294 msgid "Approval step" msgstr "承認ステップ" -#: tickets/models/ticket/general.py:287 +#: tickets/models/ticket/general.py:297 msgid "Relation snapshot" msgstr "製造オーダスナップショット" -#: tickets/models/ticket/general.py:380 +#: tickets/models/ticket/general.py:390 msgid "Please try again" msgstr "もう一度お試しください" -#: tickets/models/ticket/general.py:387 +#: tickets/models/ticket/general.py:421 msgid "Super ticket" msgstr "スーパーチケット" @@ -5357,27 +5429,27 @@ msgstr "ログインシステムユーザー" msgid "Login datetime" msgstr "ログイン日時" -#: tickets/notifications.py:64 +#: tickets/notifications.py:63 msgid "Ticket basic info" msgstr "チケット基本情報" -#: tickets/notifications.py:65 +#: tickets/notifications.py:64 msgid "Ticket applied info" msgstr "チケット適用情報" -#: tickets/notifications.py:116 -msgid "Your has a new ticket" +#: tickets/notifications.py:109 +msgid "Your has a new ticket, applicant - {}" msgstr "新しいチケットがあります- {}" -#: tickets/notifications.py:120 +#: tickets/notifications.py:113 msgid "{}: New Ticket - {} ({})" msgstr "新しいチケット- {} ({})" -#: tickets/notifications.py:164 +#: tickets/notifications.py:157 msgid "Your ticket has been processed, processor - {}" msgstr "チケットが処理されました。プロセッサー- {}" -#: tickets/notifications.py:168 +#: tickets/notifications.py:161 msgid "Ticket has processed - {} ({})" msgstr "チケットが処理済み- {} ({})" @@ -5398,19 +5470,19 @@ msgid "Processor" msgstr "プロセッサ" #: tickets/serializers/ticket/common.py:16 -#: tickets/serializers/ticket/common.py:68 +#: tickets/serializers/ticket/common.py:79 msgid "Created by ticket ({}-{})" msgstr "チケットで作成 ({}-{})" -#: tickets/serializers/ticket/common.py:58 +#: tickets/serializers/ticket/common.py:69 msgid "The expiration date should be greater than the start date" msgstr "有効期限は開始日より大きくする必要があります" -#: tickets/serializers/ticket/common.py:74 +#: tickets/serializers/ticket/common.py:85 msgid "Permission named `{}` already exists" msgstr "'{}'という名前の権限は既に存在します" -#: tickets/serializers/ticket/ticket.py:89 +#: tickets/serializers/ticket/ticket.py:92 msgid "The ticket flow `{}` does not exist" msgstr "チケットフロー '{}'が存在しない" @@ -5581,7 +5653,7 @@ msgstr "強制有効" msgid "Local" msgstr "ローカル" -#: users/models/user.py:673 users/serializers/user.py:147 +#: users/models/user.py:673 users/serializers/user.py:149 msgid "Is service account" msgstr "サービスアカウントです" @@ -5680,109 +5752,105 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする msgid "The newly set password is inconsistent" msgstr "新しく設定されたパスワードが一致しない" -#: users/serializers/profile.py:149 users/serializers/user.py:144 +#: users/serializers/profile.py:149 users/serializers/user.py:146 msgid "Is first login" msgstr "最初のログインです" -#: users/serializers/user.py:26 +#: users/serializers/user.py:28 msgid "System roles" msgstr "システムの役割" -#: users/serializers/user.py:31 +#: users/serializers/user.py:33 msgid "Org roles" msgstr "組織ロール" -#: users/serializers/user.py:33 +#: users/serializers/user.py:35 msgid "System roles display" msgstr "システムロール表示" -#: users/serializers/user.py:34 +#: users/serializers/user.py:36 msgid "Org roles display" msgstr "組織ロール表示" -#: users/serializers/user.py:79 +#: users/serializers/user.py:81 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:27 msgid "Password strategy" msgstr "パスワード戦略" -#: users/serializers/user.py:81 +#: users/serializers/user.py:83 msgid "MFA enabled" msgstr "MFA有効化" -#: users/serializers/user.py:82 +#: users/serializers/user.py:84 msgid "MFA force enabled" msgstr "MFAフォース有効化" -#: users/serializers/user.py:84 +#: users/serializers/user.py:86 msgid "MFA level display" msgstr "MFAレベル表示" -#: users/serializers/user.py:86 +#: users/serializers/user.py:88 msgid "Login blocked" msgstr "ログインブロック" -#: users/serializers/user.py:89 +#: users/serializers/user.py:91 msgid "Can public key authentication" msgstr "公開鍵認証が可能" -#: users/serializers/user.py:149 +#: users/serializers/user.py:151 msgid "Avatar url" msgstr "アバターURL" -#: users/serializers/user.py:151 +#: users/serializers/user.py:153 msgid "Groups name" msgstr "グループ名" -#: users/serializers/user.py:152 +#: users/serializers/user.py:154 msgid "Source name" msgstr "ソース名" -#: users/serializers/user.py:153 +#: users/serializers/user.py:155 msgid "Organization role name" msgstr "組織の役割名" -#: users/serializers/user.py:154 +#: users/serializers/user.py:156 msgid "Super role name" msgstr "スーパーロール名" -#: users/serializers/user.py:155 +#: users/serializers/user.py:157 msgid "Total role name" msgstr "合計ロール名" -#: users/serializers/user.py:157 +#: users/serializers/user.py:159 msgid "Is wecom bound" msgstr "企業の微信をバインドしているかどうか" -#: users/serializers/user.py:158 +#: users/serializers/user.py:160 msgid "Is dingtalk bound" msgstr "ピンをバインドしているかどうか" -#: users/serializers/user.py:159 +#: users/serializers/user.py:161 msgid "Is feishu bound" msgstr "飛本を縛ったかどうか" -#: users/serializers/user.py:160 +#: users/serializers/user.py:162 msgid "Is OTP bound" msgstr "仮想MFAがバインドされているか" -#: users/serializers/user.py:162 +#: users/serializers/user.py:164 msgid "System role name" msgstr "システムロール名" -#: users/serializers/user.py:202 -msgid "User cannot self-update fields: {}" -msgstr "ユーザーは自分のフィールドを更新できません: {}" - -#: users/serializers/user.py:259 +#: users/serializers/user.py:263 msgid "Select users" msgstr "ユーザーの選択" -#: users/serializers/user.py:260 +#: users/serializers/user.py:264 msgid "For security, only list several users" msgstr "セキュリティのために、複数のユーザーのみをリストします" -#: users/serializers/user.py:295 +#: users/serializers/user.py:299 msgid "name not unique" msgstr "名前が一意ではない" @@ -6323,10 +6391,6 @@ msgstr "クラウドセンター" msgid "Provider" msgstr "プロバイダー" -#: xpack/plugins/cloud/models.py:34 -msgid "Validity" -msgstr "有効性" - #: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "クラウドアカウント" @@ -6335,11 +6399,11 @@ msgstr "クラウドアカウント" msgid "Test cloud account" msgstr "クラウドアカウントのテスト" -#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:67 msgid "Account" msgstr "アカウント" -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:38 msgid "Regions" msgstr "リージョン" @@ -6347,19 +6411,19 @@ msgstr "リージョン" msgid "Hostname strategy" msgstr "ホスト名戦略" -#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:68 msgid "Unix admin user" msgstr "Unix adminユーザー" -#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:69 msgid "Windows admin user" msgstr "Windows管理者" -#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:46 msgid "IP network segment group" msgstr "IPネットワークセグメントグループ" -#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:72 msgid "Always update" msgstr "常に更新" @@ -6633,32 +6697,37 @@ msgstr "ファイルはJSON形式です。" #: xpack/plugins/cloud/serializers/task.py:29 msgid "" -"The IP address that is first matched to will be used as the IP of the " -"created asset.
The default * indicates a random match.
Format for " -"comma-delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" +"Only instances matching the IP range will be synced.
If the instance " +"contains multiple IP addresses, the first IP address that matches will be " +"used as the IP for the created asset.
The default value of * means sync " +"all instances and randomly match IP addresses.
Format for comma-" +"delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" msgstr "" -"最初に一致するIPアドレスは、作成された資産のIPとして使用されます。
デフォ" -"ルト*はランダムマッチングを表します。
カンマで区切られた文字列で書式設" -"定,たとえば:192.168.1.0/24,10.1.1.1-10.1.1.20" +"IP範囲に一致するインスタンスのみが同期されます。
" +"インスタンスに複数のIPアドレスが含まれている場合、一致する最初のIPアドレスが作成されたアセットのIPとして使用されます。
" +"デフォルト値の*は、すべてのインスタンスを同期し、IPアドレスをランダムに一致させることを意味します。
" +"形式はコンマ区切りの文字列です。例:192.168.1.0/24,10.1.1.1-10.1.1.20" -#: xpack/plugins/cloud/serializers/task.py:35 + + +#: xpack/plugins/cloud/serializers/task.py:36 msgid "History count" msgstr "実行回数" -#: xpack/plugins/cloud/serializers/task.py:36 +#: xpack/plugins/cloud/serializers/task.py:37 msgid "Instance count" msgstr "インスタンス数" -#: xpack/plugins/cloud/serializers/task.py:65 +#: xpack/plugins/cloud/serializers/task.py:66 msgid "Linux admin user" msgstr "Linux管理者" -#: xpack/plugins/cloud/serializers/task.py:70 +#: xpack/plugins/cloud/serializers/task.py:71 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定期的な表示" -#: xpack/plugins/cloud/utils.py:68 +#: xpack/plugins/cloud/utils.py:69 msgid "Account unavailable" msgstr "利用できないアカウント" @@ -6682,11 +6751,7 @@ msgstr "資産は空です。ノードを変更してください" msgid "Executed times" msgstr "実行時間" -#: xpack/plugins/interface/api.py:50 -msgid "It is already in the default setting state!" -msgstr "すでにデフォルトの設定状態です!" - -#: xpack/plugins/interface/api.py:53 +#: xpack/plugins/interface/api.py:52 msgid "Restore default successfully." msgstr "デフォルトの復元に成功しました。" @@ -6750,17 +6815,5 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" -#~ msgid "Classic green" -#~ msgstr "クラシックグリーン" - -#~ msgid "Chinese red" -#~ msgstr "チャイナレッド" - -#~ msgid "Tech blue" -#~ msgstr "テクノロジーブルー" - -#~ msgid "Deep black" -#~ msgstr "深き黒" - -#~ msgid "Filters" -#~ msgstr "フィルター" +#~ msgid "User cannot self-update fields: {}" +#~ msgstr "ユーザーは自分のフィールドを更新できません: {}" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 92b8e52cb..dcc55a77c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfbd815ff09d4b7c5f64af1eb745b81937a267d469ffbcba93c849feada2a8a8 -size 104695 +oid sha256:1efe7f07b0877357a42a7f93e075c152e2bd8ee7adc20bcab17427a86cce5ed3 +size 105644 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index c66a34a44..892d7cb6f 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: 2022-07-06 16:47+0800\n" +"POT-Creation-Date: 2022-07-12 17:58+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -46,7 +46,7 @@ msgstr "优先级" msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" -#: acls/models/base.py:31 authentication/models.py:18 +#: acls/models/base.py:31 authentication/models.py:21 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:88 terminal/models/sharing.py:28 tickets/const.py:39 msgid "Active" @@ -57,12 +57,12 @@ msgstr "激活中" #: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 #: assets/models/cmd_filter.py:96 assets/models/domain.py:24 -#: assets/models/domain.py:64 assets/models/group.py:23 +#: assets/models/domain.py:65 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/endpoint.py:21 terminal/models/endpoint.py:92 #: terminal/models/storage.py:29 terminal/models/terminal.py:114 -#: tickets/models/comment.py:32 tickets/models/ticket/general.py:278 +#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 @@ -87,8 +87,8 @@ msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:62 audits/models.py:87 audits/serializers.py:100 -#: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:118 rbac/models/rolebinding.py:41 +#: authentication/models.py:54 authentication/models.py:78 orgs/models.py:214 +#: perms/models/base.py:84 rbac/builtin.py:118 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 #: terminal/models/sharing.py:33 terminal/notifications.py:91 @@ -130,6 +130,7 @@ msgstr "系统用户" #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 #: assets/serializers/system_user.py:268 audits/models.py:39 +#: authentication/models.py:66 authentication/models.py:90 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 #: terminal/notifications.py:90 @@ -155,7 +156,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: assets/models/gathered_user.py:15 audits/models.py:121 #: authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models.py:69 +#: authentication/models.py:245 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:659 @@ -177,7 +178,7 @@ msgstr "" #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 #: applications/serializers/attrs/application_type/mysql_workbench.py:18 -#: assets/models/asset.py:210 assets/models/domain.py:60 +#: assets/models/asset.py:210 assets/models/domain.py:61 #: assets/serializers/account.py:13 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 @@ -199,7 +200,7 @@ msgid "" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 -#: assets/models/domain.py:62 assets/models/user.py:252 +#: assets/models/domain.py:63 assets/models/user.py:252 #: terminal/serializers/session.py:31 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "协议" @@ -256,13 +257,14 @@ msgstr "自定义" #: applications/models/account.py:12 applications/models/application.py:234 #: assets/models/backup.py:32 assets/models/cmd_filter.py:45 +#: authentication/models.py:67 authentication/models.py:95 #: perms/models/application_permission.py:28 msgid "Application" msgstr "应用程序" #: applications/models/account.py:15 assets/models/authbook.py:20 #: assets/models/cmd_filter.py:42 assets/models/user.py:342 audits/models.py:40 -#: perms/models/application_permission.py:33 +#: authentication/models.py:83 perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:36 terminal/models/session.py:48 #: xpack/plugins/change_auth_plan/models/app.py:36 @@ -300,19 +302,19 @@ msgstr "类别" #: applications/models/application.py:222 #: applications/serializers/application.py:101 assets/models/backup.py:49 #: assets/models/cmd_filter.py:82 assets/models/user.py:250 -#: perms/models/application_permission.py:24 +#: authentication/models.py:70 perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 -#: terminal/models/storage.py:58 terminal/models/storage.py:142 +#: terminal/models/storage.py:58 terminal/models/storage.py:143 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 -#: tickets/models/ticket/general.py:263 +#: tickets/models/ticket/general.py:273 #: xpack/plugins/change_auth_plan/models/app.py:28 #: xpack/plugins/change_auth_plan/models/app.py:153 msgid "Type" msgstr "类型" #: applications/models/application.py:226 assets/models/asset.py:217 -#: assets/models/domain.py:29 assets/models/domain.py:63 +#: assets/models/domain.py:29 assets/models/domain.py:64 msgid "Domain" msgstr "网域" @@ -338,7 +340,8 @@ msgstr "类别名称" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 #: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:34 -#: audits/serializers.py:29 perms/serializers/application/permission.py:19 +#: audits/serializers.py:29 authentication/serializers/connection_token.py:22 +#: perms/serializers/application/permission.py:19 #: tickets/serializers/flow.py:48 tickets/serializers/ticket/ticket.py:17 msgid "Type display" msgstr "类型名称" @@ -365,7 +368,7 @@ msgid "Date updated" msgstr "更新日期" #: applications/serializers/application.py:121 -#: applications/serializers/application.py:166 +#: applications/serializers/application.py:166 authentication/models.py:99 msgid "Application display" msgstr "应用名称" @@ -393,7 +396,7 @@ msgstr "主机" #: applications/serializers/attrs/application_type/pgsql.py:10 #: applications/serializers/attrs/application_type/redis.py:10 #: applications/serializers/attrs/application_type/sqlserver.py:10 -#: assets/models/asset.py:214 assets/models/domain.py:61 +#: assets/models/asset.py:214 assets/models/domain.py:62 #: settings/serializers/auth/radius.py:15 #: xpack/plugins/cloud/serializers/account_attrs.py:71 msgid "Port" @@ -408,6 +411,8 @@ msgstr "应用路径" #: applications/serializers/attrs/application_category/remote_app.py:44 #: assets/serializers/system_user.py:167 +#: tickets/serializers/ticket/apply_application.py:35 +#: tickets/serializers/ticket/common.py:59 #: xpack/plugins/change_auth_plan/serializers/asset.py:67 #: xpack/plugins/change_auth_plan/serializers/asset.py:70 #: xpack/plugins/change_auth_plan/serializers/asset.py:73 @@ -495,7 +500,7 @@ msgid "Charset" msgstr "编码" #: assets/models/asset.py:141 assets/serializers/asset.py:176 -#: tickets/models/ticket/general.py:288 +#: tickets/models/ticket/general.py:298 msgid "Meta" msgstr "元数据" @@ -517,7 +522,7 @@ msgstr "制造商" msgid "Model" msgstr "型号" -#: assets/models/asset.py:170 tickets/models/ticket/general.py:286 +#: assets/models/asset.py:170 tickets/models/ticket/general.py:296 msgid "Serial number" msgstr "序列号" @@ -567,7 +572,7 @@ msgstr "主机名原始" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:43 msgid "Protocols" msgstr "协议组" @@ -579,8 +584,8 @@ msgid "Nodes" msgstr "节点" #: assets/models/asset.py:219 assets/models/cmd_filter.py:47 -#: assets/models/domain.py:65 assets/models/label.py:22 -#: users/serializers/user.py:145 +#: assets/models/domain.py:66 assets/models/label.py:22 +#: users/serializers/user.py:147 msgid "Is active" msgstr "激活" @@ -758,7 +763,7 @@ msgstr "失败" msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:40 authentication/models.py:72 +#: assets/models/base.py:40 authentication/models.py:248 msgid "Date verified" msgstr "校验日期" @@ -768,7 +773,7 @@ msgstr "校验日期" #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 -#: users/forms/profile.py:22 users/serializers/user.py:92 +#: users/forms/profile.py:22 users/serializers/user.py:94 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -892,24 +897,24 @@ msgstr "生成的正则表达式有误" msgid "Command confirm" msgstr "命令复核" -#: assets/models/domain.py:72 +#: assets/models/domain.py:73 msgid "Gateway" msgstr "网关" -#: assets/models/domain.py:74 +#: assets/models/domain.py:75 msgid "Test gateway" msgstr "测试网关" -#: assets/models/domain.py:130 +#: assets/models/domain.py:131 #, python-brace-format msgid "Unable to connect to port {port} on {ip}" msgstr "无法连接到 {ip} 上的端口 {port}" -#: assets/models/domain.py:133 xpack/plugins/cloud/providers/fc.py:48 +#: assets/models/domain.py:134 xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "认证失败" -#: assets/models/domain.py:135 assets/models/domain.py:157 +#: assets/models/domain.py:136 assets/models/domain.py:158 msgid "Connect failed" msgstr "连接失败" @@ -966,7 +971,7 @@ msgid "Parent key" msgstr "ssh私钥" #: assets/models/node.py:559 assets/serializers/system_user.py:267 -#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:70 msgid "Node" msgstr "节点" @@ -1025,7 +1030,7 @@ msgid "SFTP Root" msgstr "SFTP根路径" #: assets/models/user.py:258 assets/serializers/system_user.py:37 -#: authentication/models.py:49 +#: authentication/models.py:52 msgid "Token" msgstr "Token" @@ -1074,7 +1079,7 @@ msgstr "" "置加密密码" #: assets/serializers/account.py:36 assets/serializers/account.py:87 -#: assets/serializers/account_history.py:10 +#: assets/serializers/account_history.py:10 authentication/models.py:87 msgid "System user display" msgstr "系统用户名称" @@ -1510,7 +1515,7 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:128 terminal/models/status.py:33 -#: tickets/models/ticket/general.py:271 xpack/plugins/cloud/models.py:175 +#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:175 #: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "状态" @@ -1564,7 +1569,8 @@ msgstr "运行用户" msgid "Run as display" msgstr "运行用户名称" -#: audits/serializers.py:102 rbac/serializers/rolebinding.py:21 +#: audits/serializers.py:102 authentication/models.py:81 +#: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "用户名称" @@ -1586,192 +1592,194 @@ msgstr "认证令牌" msgid "WeCom" msgstr "企业微信" -#: audits/signal_handlers.py:54 authentication/views/dingtalk.py:179 +#: audits/signal_handlers.py:54 authentication/views/feishu.py:144 +#: authentication/views/login.py:79 notifications/backends/__init__.py:14 +#: users/models/user.py:722 +msgid "FeiShu" +msgstr "飞书" + +#: audits/signal_handlers.py:55 authentication/views/dingtalk.py:179 #: authentication/views/login.py:73 notifications/backends/__init__.py:12 #: users/models/user.py:721 msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:55 authentication/models.py:76 +#: audits/signal_handlers.py:56 authentication/models.py:252 msgid "Temporary token" msgstr "临时密码" -#: audits/signal_handlers.py:67 +#: audits/signal_handlers.py:68 msgid "User and Group" msgstr "用户与用户组" -#: audits/signal_handlers.py:68 +#: audits/signal_handlers.py:69 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} 加入 {UserGroup}" -#: audits/signal_handlers.py:69 +#: audits/signal_handlers.py:70 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} 离开 {UserGroup}" -#: audits/signal_handlers.py:72 +#: audits/signal_handlers.py:73 msgid "Asset and SystemUser" msgstr "资产与系统用户" -#: audits/signal_handlers.py:73 +#: audits/signal_handlers.py:74 #, python-brace-format msgid "{Asset} ADD {SystemUser}" msgstr "{Asset} 添加 {SystemUser}" -#: audits/signal_handlers.py:74 +#: audits/signal_handlers.py:75 #, python-brace-format msgid "{Asset} REMOVE {SystemUser}" msgstr "{Asset} 移除 {SystemUser}" -#: audits/signal_handlers.py:77 +#: audits/signal_handlers.py:78 msgid "Node and Asset" msgstr "节点与资产" -#: audits/signal_handlers.py:78 +#: audits/signal_handlers.py:79 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 添加 {Asset}" -#: audits/signal_handlers.py:79 +#: audits/signal_handlers.py:80 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 移除 {Asset}" -#: audits/signal_handlers.py:82 +#: audits/signal_handlers.py:83 msgid "User asset permissions" msgstr "用户资产授权" -#: audits/signal_handlers.py:83 +#: audits/signal_handlers.py:84 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 添加 {User}" -#: audits/signal_handlers.py:84 +#: audits/signal_handlers.py:85 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 移除 {User}" -#: audits/signal_handlers.py:87 +#: audits/signal_handlers.py:88 msgid "User group asset permissions" msgstr "用户组资产授权" -#: audits/signal_handlers.py:88 +#: audits/signal_handlers.py:89 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:89 +#: audits/signal_handlers.py:90 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:92 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:93 perms/models/asset_permission.py:29 msgid "Asset permission" msgstr "资产授权" -#: audits/signal_handlers.py:93 +#: audits/signal_handlers.py:94 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 添加 {Asset}" -#: audits/signal_handlers.py:94 +#: audits/signal_handlers.py:95 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 移除 {Asset}" -#: audits/signal_handlers.py:97 +#: audits/signal_handlers.py:98 msgid "Node permission" msgstr "节点授权" -#: audits/signal_handlers.py:98 +#: audits/signal_handlers.py:99 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 添加 {Node}" -#: audits/signal_handlers.py:99 +#: audits/signal_handlers.py:100 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 移除 {Node}" -#: audits/signal_handlers.py:102 +#: audits/signal_handlers.py:103 msgid "Asset permission and SystemUser" msgstr "资产授权与系统用户" -#: audits/signal_handlers.py:103 +#: audits/signal_handlers.py:104 #, python-brace-format msgid "{AssetPermission} ADD {SystemUser}" msgstr "{AssetPermission} 添加 {SystemUser}" -#: audits/signal_handlers.py:104 +#: audits/signal_handlers.py:105 #, python-brace-format msgid "{AssetPermission} REMOVE {SystemUser}" msgstr "{AssetPermission} 移除 {SystemUser}" -#: audits/signal_handlers.py:107 +#: audits/signal_handlers.py:108 msgid "User application permissions" msgstr "用户应用授权" -#: audits/signal_handlers.py:108 +#: audits/signal_handlers.py:109 #, python-brace-format msgid "{ApplicationPermission} ADD {User}" msgstr "{ApplicationPermission} 添加 {User}" -#: audits/signal_handlers.py:109 +#: audits/signal_handlers.py:110 #, python-brace-format msgid "{ApplicationPermission} REMOVE {User}" msgstr "{ApplicationPermission} 移除 {User}" -#: audits/signal_handlers.py:112 +#: audits/signal_handlers.py:113 msgid "User group application permissions" msgstr "用户组应用授权" -#: audits/signal_handlers.py:113 +#: audits/signal_handlers.py:114 #, python-brace-format msgid "{ApplicationPermission} ADD {UserGroup}" msgstr "{ApplicationPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:114 +#: audits/signal_handlers.py:115 #, python-brace-format msgid "{ApplicationPermission} REMOVE {UserGroup}" msgstr "{ApplicationPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:117 perms/models/application_permission.py:38 +#: audits/signal_handlers.py:118 perms/models/application_permission.py:38 msgid "Application permission" msgstr "应用授权" -#: audits/signal_handlers.py:118 +#: audits/signal_handlers.py:119 #, python-brace-format msgid "{ApplicationPermission} ADD {Application}" msgstr "{ApplicationPermission} 添加 {Application}" -#: audits/signal_handlers.py:119 +#: audits/signal_handlers.py:120 #, python-brace-format msgid "{ApplicationPermission} REMOVE {Application}" msgstr "{ApplicationPermission} 移除 {Application}" -#: audits/signal_handlers.py:122 +#: audits/signal_handlers.py:123 msgid "Application permission and SystemUser" msgstr "应用授权与系统用户" -#: audits/signal_handlers.py:123 +#: audits/signal_handlers.py:124 #, python-brace-format msgid "{ApplicationPermission} ADD {SystemUser}" msgstr "{ApplicationPermission} 添加 {SystemUser}" -#: audits/signal_handlers.py:124 +#: audits/signal_handlers.py:125 #, python-brace-format msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}" -#: authentication/api/confirm.py:33 +#: authentication/api/confirm.py:40 msgid "This action require verify your MFA" msgstr "此操作需要验证您的 MFA" -#: authentication/api/connection_token.py:326 -msgid "Invalid token" -msgstr "无效的令牌" - #: authentication/api/mfa.py:59 msgid "Current user not support mfa type: {}" msgstr "当前用户不支持 MFA 类型: {}" @@ -2061,47 +2069,87 @@ msgstr "该 MFA ({}) 方式没有启用" msgid "Please change your password" msgstr "请修改密码" -#: authentication/models.py:34 +#: authentication/models.py:37 msgid "Access key" msgstr "Access key" -#: authentication/models.py:41 +#: authentication/models.py:44 msgid "Private Token" msgstr "SSH 密钥" -#: authentication/models.py:50 +#: authentication/models.py:53 msgid "Expired" msgstr "过期时间" -#: authentication/models.py:54 +#: authentication/models.py:57 msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:62 -msgid "Connection token" -msgstr "连接令牌" - -#: authentication/models.py:64 -msgid "Can view connection token secret" -msgstr "可以查看连接令牌密文" - -#: authentication/models.py:70 +#: authentication/models.py:72 authentication/models.py:246 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:17 msgid "Secret" msgstr "密钥" -#: authentication/models.py:71 -msgid "Verified" -msgstr "已校验" - -#: authentication/models.py:73 perms/models/base.py:90 -#: tickets/models/ticket/apply_application.py:26 +#: authentication/models.py:74 authentication/models.py:249 +#: perms/models/base.py:90 tickets/models/ticket/apply_application.py:26 #: tickets/models/ticket/apply_asset.py:24 users/models/user.py:703 msgid "Date expired" msgstr "失效日期" -#: authentication/models.py:92 +#: authentication/models.py:93 +msgid "Asset display" +msgstr "资产名称" + +#: authentication/models.py:104 +msgid "Connection token" +msgstr "连接令牌" + +#: authentication/models.py:106 +msgid "Can view connection token secret" +msgstr "可以查看连接令牌密文" + +#: authentication/models.py:141 +msgid "Connection token expired at: {}" +msgstr "连接令牌过期: {}" + +#: authentication/models.py:146 +msgid "User not exists" +msgstr "用户不存在" + +#: authentication/models.py:150 +msgid "User invalid, disabled or expired" +msgstr "用户无效,已禁用或已过期" + +#: authentication/models.py:155 +msgid "System user not exists" +msgstr "系统用户不存在" + +#: authentication/models.py:161 +msgid "Asset not exists" +msgstr "资产不存在" + +#: authentication/models.py:165 +msgid "Asset inactive" +msgstr "资产未激活" + +#: authentication/models.py:172 +msgid "User has no permission to access asset or permission expired" +msgstr "用户没有权限访问资产或权限已过期" + +#: authentication/models.py:180 +msgid "Application not exists" +msgstr "应用不存在" + +#: authentication/models.py:187 +msgid "User has no permission to access application or permission expired" +msgstr "用户没有权限访问应用或权限已过期" + +#: authentication/models.py:247 +msgid "Verified" +msgstr "已校验" + +#: authentication/models.py:268 msgid "Super connection token" msgstr "超级连接令牌" @@ -2113,11 +2161,20 @@ msgstr "异地登录提醒" msgid "binding reminder" msgstr "绑定提醒" +#: authentication/serializers/connection_token.py:23 +#: xpack/plugins/cloud/models.py:34 +msgid "Validity" +msgstr "有效" + +#: authentication/serializers/connection_token.py:73 +msgid "Asset or application required" +msgstr "资产或应用必填" + #: authentication/serializers/token.py:79 #: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:41 #: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:146 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:148 msgid "Is valid" msgstr "账号是否有效" @@ -2399,11 +2456,6 @@ msgstr "飞书查询用户失败" msgid "The FeiShu is already bound to another user" msgstr "该飞书已经绑定其他用户" -#: authentication/views/feishu.py:144 authentication/views/login.py:79 -#: notifications/backends/__init__.py:14 users/models/user.py:722 -msgid "FeiShu" -msgstr "飞书" - #: authentication/views/feishu.py:145 msgid "Binding FeiShu successfully" msgstr "绑定 飞书 成功" @@ -2498,7 +2550,7 @@ msgstr "%(name)s 创建成功" msgid "%(name)s was updated successfully" msgstr "%(name)s 更新成功" -#: common/db/encoder.py:10 +#: common/db/encoder.py:11 msgid "ugettext_lazy" msgstr "ugettext_lazy" @@ -2920,7 +2972,7 @@ msgstr "组织管理" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 #: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:290 tickets/serializers/ticket/ticket.py:64 +#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:64 msgid "Organization" msgstr "组织" @@ -3056,8 +3108,8 @@ msgstr "组织 ({}) 的应用授权" #: perms/serializers/application/permission.py:21 #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 -#: perms/serializers/asset/permission.py:44 users/serializers/user.py:87 -#: users/serializers/user.py:148 +#: perms/serializers/asset/permission.py:44 users/serializers/user.py:89 +#: users/serializers/user.py:150 msgid "Is expired" msgstr "已过期" @@ -3325,7 +3377,7 @@ msgstr "我的应用" msgid "Ticket comment" msgstr "工单评论" -#: rbac/tree.py:115 tickets/models/ticket/general.py:295 +#: rbac/tree.py:115 tickets/models/ticket/general.py:305 msgid "Ticket" msgstr "工单管理" @@ -4546,6 +4598,10 @@ msgid "" "Remote Application publisher" msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远程应用的程序" +#: templates/resource_download.html:51 +msgid "Offline video player" +msgstr "离线录像播放器" + #: terminal/api/endpoint.py:26 msgid "Not found protocol query params" msgstr "" @@ -4810,7 +4866,7 @@ msgstr "链接过期" msgid "User not allowed to join" msgstr "来源 IP 不被允许登录" -#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:58 +#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:59 msgid "Joiner" msgstr "加入者" @@ -4867,11 +4923,11 @@ msgstr "运行时间" msgid "Default storage" msgstr "默认存储" -#: terminal/models/storage.py:136 terminal/models/terminal.py:108 +#: terminal/models/storage.py:137 terminal/models/terminal.py:108 msgid "Command storage" msgstr "命令存储" -#: terminal/models/storage.py:196 terminal/models/terminal.py:109 +#: terminal/models/storage.py:197 terminal/models/terminal.py:109 msgid "Replay storage" msgstr "录像存储" @@ -5120,7 +5176,19 @@ msgid "" msgstr "" "通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" -#: tickets/handlers/base.py:72 +#: tickets/handlers/base.py:79 +msgid "Change field" +msgstr "变更字段" + +#: tickets/handlers/base.py:79 +msgid "Before change" +msgstr "变更前" + +#: tickets/handlers/base.py:79 +msgid "After change" +msgstr "变更后" + +#: tickets/handlers/base.py:91 msgid "{} {} the ticket" msgstr "{} {} 工单" @@ -5136,8 +5204,8 @@ msgstr "申请登录的城市" msgid "Applied login datetime" msgstr "申请登录的日期" -#: tickets/models/comment.py:13 tickets/models/ticket/general.py:39 -#: tickets/models/ticket/general.py:267 +#: tickets/models/comment.py:13 tickets/models/ticket/general.py:41 +#: tickets/models/ticket/general.py:277 msgid "State" msgstr "状态" @@ -5154,7 +5222,7 @@ msgid "Body" msgstr "内容" #: tickets/models/flow.py:20 tickets/models/flow.py:62 -#: tickets/models/ticket/general.py:35 +#: tickets/models/ticket/general.py:37 msgid "Approve level" msgstr "审批级别" @@ -5180,8 +5248,8 @@ msgstr "工单会话" #: tickets/models/ticket/apply_application.py:11 #: tickets/models/ticket/apply_asset.py:13 -msgid "Apply name" -msgstr "应用名称" +msgid "Permission name" +msgstr "授权规则名称" #: tickets/models/ticket/apply_application.py:20 msgid "Apply applications" @@ -5229,39 +5297,39 @@ msgstr "来自命令过滤规则" msgid "From cmd filter rule" msgstr "来自命令过滤规则" -#: tickets/models/ticket/general.py:70 +#: tickets/models/ticket/general.py:72 msgid "Ticket step" msgstr "工单步骤" -#: tickets/models/ticket/general.py:88 +#: tickets/models/ticket/general.py:90 msgid "Ticket assignee" msgstr "工单受理人" -#: tickets/models/ticket/general.py:260 +#: tickets/models/ticket/general.py:270 msgid "Title" msgstr "标题" -#: tickets/models/ticket/general.py:276 +#: tickets/models/ticket/general.py:286 msgid "Applicant" msgstr "申请人" -#: tickets/models/ticket/general.py:281 +#: tickets/models/ticket/general.py:291 msgid "TicketFlow" msgstr "工单流程" -#: tickets/models/ticket/general.py:284 +#: tickets/models/ticket/general.py:294 msgid "Approval step" msgstr "审批步骤" -#: tickets/models/ticket/general.py:287 +#: tickets/models/ticket/general.py:297 msgid "Relation snapshot" msgstr "工单快照" -#: tickets/models/ticket/general.py:380 +#: tickets/models/ticket/general.py:390 msgid "Please try again" msgstr "请再次尝试" -#: tickets/models/ticket/general.py:387 +#: tickets/models/ticket/general.py:421 msgid "Super ticket" msgstr "超级工单" @@ -5281,27 +5349,27 @@ msgstr "登录系统用户" msgid "Login datetime" msgstr "登录日期" -#: tickets/notifications.py:64 +#: tickets/notifications.py:63 msgid "Ticket basic info" msgstr "工单基本信息" -#: tickets/notifications.py:65 +#: tickets/notifications.py:64 msgid "Ticket applied info" msgstr "工单申请信息" -#: tickets/notifications.py:116 -msgid "Your has a new ticket" +#: tickets/notifications.py:109 +msgid "Your has a new ticket, applicant - {}" msgstr "你有一个新的工单, 申请人 - {}" -#: tickets/notifications.py:120 +#: tickets/notifications.py:113 msgid "{}: New Ticket - {} ({})" msgstr "新工单 - {} ({})" -#: tickets/notifications.py:164 +#: tickets/notifications.py:157 msgid "Your ticket has been processed, processor - {}" msgstr "你的工单已被处理, 处理人 - {}" -#: tickets/notifications.py:168 +#: tickets/notifications.py:161 msgid "Ticket has processed - {} ({})" msgstr "你的工单已被处理, 处理人 - {} ({})" @@ -5322,19 +5390,19 @@ msgid "Processor" msgstr "处理人" #: tickets/serializers/ticket/common.py:16 -#: tickets/serializers/ticket/common.py:68 +#: tickets/serializers/ticket/common.py:79 msgid "Created by ticket ({}-{})" msgstr "通过工单创建 ({}-{})" -#: tickets/serializers/ticket/common.py:58 +#: tickets/serializers/ticket/common.py:69 msgid "The expiration date should be greater than the start date" msgstr "过期时间要大于开始时间" -#: tickets/serializers/ticket/common.py:74 +#: tickets/serializers/ticket/common.py:85 msgid "Permission named `{}` already exists" msgstr "授权名称 `{}` 已存在" -#: tickets/serializers/ticket/ticket.py:89 +#: tickets/serializers/ticket/ticket.py:92 msgid "The ticket flow `{}` does not exist" msgstr "工单流程 `{}` 不存在" @@ -5503,7 +5571,7 @@ msgstr "强制启用" msgid "Local" msgstr "数据库" -#: users/models/user.py:673 users/serializers/user.py:147 +#: users/models/user.py:673 users/serializers/user.py:149 msgid "Is service account" msgstr "服务账号" @@ -5602,109 +5670,105 @@ msgstr "新密码不能是最近 {} 次的密码" msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:149 users/serializers/user.py:144 +#: users/serializers/profile.py:149 users/serializers/user.py:146 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:26 +#: users/serializers/user.py:28 msgid "System roles" msgstr "系统角色" -#: users/serializers/user.py:31 +#: users/serializers/user.py:33 msgid "Org roles" msgstr "组织角色" -#: users/serializers/user.py:33 +#: users/serializers/user.py:35 msgid "System roles display" msgstr "系统角色显示" -#: users/serializers/user.py:34 +#: users/serializers/user.py:36 msgid "Org roles display" msgstr "组织角色显示" -#: users/serializers/user.py:79 +#: users/serializers/user.py:81 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:27 msgid "Password strategy" msgstr "密码策略" -#: users/serializers/user.py:81 +#: users/serializers/user.py:83 msgid "MFA enabled" msgstr "MFA 已启用" -#: users/serializers/user.py:82 +#: users/serializers/user.py:84 msgid "MFA force enabled" msgstr "强制 MFA" -#: users/serializers/user.py:84 +#: users/serializers/user.py:86 msgid "MFA level display" msgstr "MFA 等级名称" -#: users/serializers/user.py:86 +#: users/serializers/user.py:88 msgid "Login blocked" msgstr "登录被阻塞" -#: users/serializers/user.py:89 +#: users/serializers/user.py:91 msgid "Can public key authentication" msgstr "能否公钥认证" -#: users/serializers/user.py:149 +#: users/serializers/user.py:151 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:151 +#: users/serializers/user.py:153 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:152 +#: users/serializers/user.py:154 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:153 +#: users/serializers/user.py:155 msgid "Organization role name" msgstr "组织角色名称" -#: users/serializers/user.py:154 +#: users/serializers/user.py:156 msgid "Super role name" msgstr "超级角色名称" -#: users/serializers/user.py:155 +#: users/serializers/user.py:157 msgid "Total role name" msgstr "汇总角色名称" -#: users/serializers/user.py:157 +#: users/serializers/user.py:159 msgid "Is wecom bound" msgstr "是否绑定了企业微信" -#: users/serializers/user.py:158 +#: users/serializers/user.py:160 msgid "Is dingtalk bound" msgstr "是否绑定了钉钉" -#: users/serializers/user.py:159 +#: users/serializers/user.py:161 msgid "Is feishu bound" msgstr "是否绑定了飞书" -#: users/serializers/user.py:160 +#: users/serializers/user.py:162 msgid "Is OTP bound" msgstr "是否绑定了虚拟 MFA" -#: users/serializers/user.py:162 +#: users/serializers/user.py:164 msgid "System role name" msgstr "系统角色名称" -#: users/serializers/user.py:202 -msgid "User cannot self-update fields: {}" -msgstr "用户不能更新自己的字段: {}" - -#: users/serializers/user.py:259 +#: users/serializers/user.py:263 msgid "Select users" msgstr "选择用户" -#: users/serializers/user.py:260 +#: users/serializers/user.py:264 msgid "For security, only list several users" msgstr "为了安全,仅列出几个用户" -#: users/serializers/user.py:295 +#: users/serializers/user.py:299 msgid "name not unique" msgstr "名称重复" @@ -6232,10 +6296,6 @@ msgstr "云管中心" msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:34 -msgid "Validity" -msgstr "有效" - #: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "云账号" @@ -6244,11 +6304,11 @@ msgstr "云账号" msgid "Test cloud account" msgstr "测试云账号" -#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:67 msgid "Account" msgstr "账号" -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:38 msgid "Regions" msgstr "地域" @@ -6256,19 +6316,19 @@ msgstr "地域" msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:68 msgid "Unix admin user" msgstr "Unix 管理员" -#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:69 msgid "Windows admin user" msgstr "Windows 管理员" -#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:46 msgid "IP network segment group" msgstr "IP网段组" -#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:72 msgid "Always update" msgstr "总是更新" @@ -6542,31 +6602,35 @@ msgstr "JSON 格式的文件" #: xpack/plugins/cloud/serializers/task.py:29 msgid "" -"The IP address that is first matched to will be used as the IP of the " -"created asset.
The default * indicates a random match.
Format for " -"comma-delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" +"Only instances matching the IP range will be synced.
If the instance " +"contains multiple IP addresses, the first IP address that matches will be " +"used as the IP for the created asset.
The default value of * means sync " +"all instances and randomly match IP addresses.
Format for comma-" +"delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" msgstr "" -"第一个匹配到的 IP 地址将被用作创建的资产的 IP。
默认值 * 表示随机匹配。" -"
格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20" +"只有匹配到 IP 段的实例会被同步。
" +"如果实例包含多个 IP 地址,那么第一个匹配到的 IP 地址将被用作创建的资产的 IP。
" +"默认值 * 表示同步所有实例和随机匹配 IP 地址。
" +"格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20" -#: xpack/plugins/cloud/serializers/task.py:35 +#: xpack/plugins/cloud/serializers/task.py:36 msgid "History count" msgstr "执行次数" -#: xpack/plugins/cloud/serializers/task.py:36 +#: xpack/plugins/cloud/serializers/task.py:37 msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/serializers/task.py:65 +#: xpack/plugins/cloud/serializers/task.py:66 msgid "Linux admin user" msgstr "Linux 管理员" -#: xpack/plugins/cloud/serializers/task.py:70 +#: xpack/plugins/cloud/serializers/task.py:71 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定时执行" -#: xpack/plugins/cloud/utils.py:68 +#: xpack/plugins/cloud/utils.py:69 msgid "Account unavailable" msgstr "账号无效" @@ -6590,11 +6654,7 @@ msgstr "资产为空,请更改节点" msgid "Executed times" msgstr "执行次数" -#: xpack/plugins/interface/api.py:50 -msgid "It is already in the default setting state!" -msgstr "当前已经是初始化状态!" - -#: xpack/plugins/interface/api.py:53 +#: xpack/plugins/interface/api.py:52 msgid "Restore default successfully." msgstr "恢复默认成功!" @@ -6658,20 +6718,5 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" -#~ msgid "Classic green" -#~ msgstr "经典绿" - -#~ msgid "Chinese red" -#~ msgstr "中国红" - -#~ msgid "Tech blue" -#~ msgstr "科技蓝" - -#~ msgid "Deep black" -#~ msgstr "深邃黑" - -#~ msgid "Filters" -#~ msgstr "过滤" - -#~ msgid "Code is invalid, {}" -#~ msgstr "验证码无效: {}" +#~ msgid "User cannot self-update fields: {}" +#~ msgstr "用户不能更新自己的字段: {}" diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py index d3dc4529d..3406271b6 100644 --- a/apps/orgs/mixins/models.py +++ b/apps/orgs/mixins/models.py @@ -41,9 +41,21 @@ class OrgManager(models.Manager): return self + def bulk_create(self, objs, batch_size=None, ignore_conflicts=False): + org = get_current_org() + for obj in objs: + if org.is_root(): + if not self.org_id: + raise ValidationError('Please save in a organization') + else: + obj.org_id = org.id + return super().bulk_create(objs, batch_size, ignore_conflicts) + + class OrgModelMixin(models.Model): - org_id = models.CharField(max_length=36, blank=True, default='', - verbose_name=_("Organization"), db_index=True) + org_id = models.CharField( + max_length=36, blank=True, default='', verbose_name=_("Organization"), db_index=True + ) objects = OrgManager() sep = '@' diff --git a/apps/perms/serializers/base.py b/apps/perms/serializers/base.py index 3d48447fb..cbed4a2f8 100644 --- a/apps/perms/serializers/base.py +++ b/apps/perms/serializers/base.py @@ -21,8 +21,12 @@ class ActionsField(serializers.MultipleChoiceField): return Action.value_to_choices(value) def to_internal_value(self, data): - if data is None: + if not self.allow_empty and not data: + self.fail('empty') + + if not data: return data + return Action.choices_to_value(data) diff --git a/apps/perms/utils/application/permission.py b/apps/perms/utils/application/permission.py index f37ca62b1..955743e25 100644 --- a/apps/perms/utils/application/permission.py +++ b/apps/perms/utils/application/permission.py @@ -80,14 +80,3 @@ def get_application_system_user_ids(user, application): def has_application_system_permission(user, application, system_user): system_user_ids = get_application_system_user_ids(user, application) return system_user.id in system_user_ids - - -def get_application_actions(user, application, system_user): - perm_ids = get_user_all_app_perm_ids(user) - actions = ApplicationPermission.objects.filter( - applications=application, system_users=system_user, - id__in=list(perm_ids) - ).values_list('actions', flat=True) - - actions = reduce(lambda x, y: x | y, actions, 0) - return actions diff --git a/apps/perms/utils/asset/permission.py b/apps/perms/utils/asset/permission.py index 86e297426..e749e630b 100644 --- a/apps/perms/utils/asset/permission.py +++ b/apps/perms/utils/asset/permission.py @@ -109,9 +109,3 @@ def get_asset_system_user_ids_with_actions_by_group(group: UserGroup, asset: Ass user_groups=group ).valid().values_list('id', flat=True).distinct() return get_asset_system_user_ids_with_actions(asset_perm_ids, asset) - - -def get_asset_actions(user, asset, system_user): - systemuser_actions_mapper = get_asset_system_user_ids_with_actions_by_user(user, asset) - actions = systemuser_actions_mapper.get(system_user.id, 0) - return actions diff --git a/apps/templates/_head_css_js.html b/apps/templates/_head_css_js.html index 2256882c5..718115dc8 100644 --- a/apps/templates/_head_css_js.html +++ b/apps/templates/_head_css_js.html @@ -22,8 +22,8 @@ diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html index dddb1143c..4b60e1e09 100644 --- a/apps/templates/resource_download.html +++ b/apps/templates/resource_download.html @@ -46,6 +46,15 @@ p { {% endif %} + +
+

JumpServer {% trans 'Offline video player' %} v0.1.5

+ +
+