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/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 828a93be5..abc9b538e 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -1,58 +1,60 @@ -# -*- 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 ) - -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 +66,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 +124,162 @@ 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 - - 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 - - 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 - serializer = self.get_serializer(data=data) - serializer.is_valid(raise_exception=True) - return serializer - - 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)) - - filename = "{}-{}-jumpserver".format(username, name) - data = { - "filename": filename, - "protocol": system_user.protocol, - "username": username, - "token": token, - "config": config - } - return data - - @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) - 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() - try: - protocol_data = self.get_client_protocol_data(serializer) - except ValueError as e: - return Response({'error': str(e)}, status=401) - - protocol_data = json.dumps(protocol_data).encode() - protocol_data = base64.b64encode(protocol_data).decode() - data = { - 'url': 'jms://{}'.format(protocol_data), - } - return Response(data=data) + token = json.dumps(data) + return filename, token -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) - - -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): +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, 'get_secret_detail': ConnectionTokenSecretSerializer, } rbac_perms = { - 'GET': 'authentication.view_connectiontoken', + '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() - 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 + 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) + self.perform_create(serializer) + token: ConnectionToken = serializer.instance + return token - value = self.get_token_from_cache(token) - if not value: - raise serializers.ValidationError('Token not found') + 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) - user = get_object_or_404(User, id=value.get('user')) - if not user.is_valid: - raise serializers.ValidationError("User not valid, disabled or expired") + @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) - 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) + @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') + def get_rdp_file(self, request, *args, **kwargs): + 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 - if not has_perm: - raise serializers.ValidationError('Permission expired or invalid') - return value, user, system_user, asset, app, expired_at, actions + @action(methods=['POST', 'GET'], detail=False, url_path='client-url') + def get_client_protocol_url(self, request, *args, **kwargs): + token = self.create_connection_token() + self.check_token_valid(token) + try: + protocol_data = self.get_client_protocol_data(token) + except ValueError as e: + 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) + } + return Response(data=data) - 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) + @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 UserSuperConnectionTokenViewSet( - BaseUserConnectionTokenViewSet, TokenCacheMixin, GenericViewSet -): +class SuperConnectionTokenViewSet(ConnectionTokenViewSet): serializer_classes = { 'default': SuperConnectionTokenSerializer, } @@ -525,10 +288,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/models.py b/apps/authentication/models.py index 54cab4fbd..c0713ac1a 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,189 @@ 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() + + 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..33e99677c --- /dev/null +++ b/apps/authentication/serializers/connection_token.py @@ -0,0 +1,186 @@ +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' +] + + +class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): + type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) + validity = serializers.BooleanField(source='is_valid', read_only=True, label=_('Validity')) + + 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', 'validity', + '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, + } + + +# +# 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() + + 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..dd1faff28 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 = [ diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index d7bf5fff9..125de4e86 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:404cc258252754b1fbbb67b04b326369c7ca0797d5245fdd98939c2206e1d8a3 -size 126727 +oid sha256:9abfbe0feec22996ffa184b7865f99d1357bad8c022bfba5b28c3f5bfbd0783f +size 127716 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 750b14423..0f9e3d288 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-11 17:46+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:20 #: 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,7 +58,7 @@ 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 @@ -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:53 authentication/models.py:77 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:65 authentication/models.py:89 #: 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:244 #: 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:66 authentication/models.py:94 #: 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:82 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,7 +307,7 @@ 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:69 perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:58 terminal/models/storage.py:142 #: tickets/models/comment.py:26 tickets/models/flow.py:57 @@ -317,7 +319,7 @@ 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:98 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" @@ -584,7 +587,7 @@ msgid "Nodes" msgstr "ノード" #: assets/models/asset.py:219 assets/models/cmd_filter.py:47 -#: assets/models/domain.py:65 assets/models/label.py:22 +#: assets/models/domain.py:66 assets/models/label.py:22 #: users/serializers/user.py:145 msgid "Is active" msgstr "アクティブです。" @@ -763,7 +766,7 @@ msgstr "失敗しました" msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:40 authentication/models.py:72 +#: assets/models/base.py:40 authentication/models.py:247 msgid "Date verified" msgstr "確認済みの日付" @@ -897,24 +900,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 "接続に失敗しました" @@ -1030,7 +1033,7 @@ msgid "SFTP Root" msgstr "SFTPルート" #: assets/models/user.py:258 assets/serializers/system_user.py:37 -#: authentication/models.py:49 +#: authentication/models.py:51 msgid "Token" msgstr "トークン" @@ -1082,7 +1085,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:86 msgid "System user display" msgstr "システムユーザー表示" @@ -1576,7 +1579,8 @@ msgstr "として実行" msgid "Run as display" msgstr "ディスプレイとして実行する" -#: audits/serializers.py:102 rbac/serializers/rolebinding.py:21 +#: audits/serializers.py:102 authentication/models.py:80 +#: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "ユーザー表示" @@ -1604,7 +1608,7 @@ msgstr "企業微信" msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:55 authentication/models.py:76 +#: audits/signal_handlers.py:55 authentication/models.py:251 msgid "Temporary token" msgstr "仮パスワード" @@ -1780,10 +1784,6 @@ msgstr "{ApplicationPermission} 削除 {SystemUser}" 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 +2082,91 @@ msgstr "MFAタイプ ({}) が有効になっていない" msgid "Please change your password" msgstr "パスワードを変更してください" -#: authentication/models.py:34 +#: authentication/models.py:36 msgid "Access key" msgstr "アクセスキー" -#: authentication/models.py:41 +#: authentication/models.py:43 msgid "Private Token" msgstr "プライベートトークン" -#: authentication/models.py:50 +#: authentication/models.py:52 msgid "Expired" msgstr "期限切れ" -#: authentication/models.py:54 +#: authentication/models.py:56 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:71 authentication/models.py:245 #: 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:73 authentication/models.py:248 +#: 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 +msgid "Asset display" +msgstr "アセット名" + +#: authentication/models.py:103 +msgid "Connection token" +msgstr "接続トークン" + +#: authentication/models.py:105 +msgid "Can view connection token secret" +msgstr "接続トークンの秘密を表示できます" + +#: authentication/models.py:140 +msgid "Connection token expired at: {}" +msgstr "接続トークンの有効期限: {}" + +#: authentication/models.py:145 +msgid "User not exists" +msgstr "ユーザーは存在しません" + +#: authentication/models.py:149 +msgid "User invalid, disabled or expired" +msgstr "ユーザーが無効、無効、または期限切れです" + +#: authentication/models.py:154 +msgid "System user not exists" +msgstr "システムユーザーが存在しません" + +#: authentication/models.py:160 +msgid "Asset not exists" +msgstr "アセットが存在しません" + +#: authentication/models.py:164 +msgid "Asset inactive" +msgstr "アセットがアクティブ化されていません" + +#: authentication/models.py:171 +msgid "User has no permission to access asset or permission expired" +msgstr "" +"ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れて" +"います" + +#: authentication/models.py:179 +msgid "Application not exists" +msgstr "アプリが存在しません" + +#: authentication/models.py:186 +msgid "User has no permission to access application or permission expired" +msgstr "" +"ユーザーがアプリにアクセスする権限を持っていないか、権限の有効期限が切れてい" +"ます" + +#: authentication/models.py:246 +msgid "Verified" +msgstr "確認済み" + +#: authentication/models.py:267 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2134,6 +2178,15 @@ 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 @@ -6327,10 +6380,6 @@ msgstr "クラウドセンター" msgid "Provider" msgstr "プロバイダー" -#: xpack/plugins/cloud/models.py:34 -msgid "Validity" -msgstr "有効性" - #: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "クラウドアカウント" @@ -6686,11 +6735,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 "デフォルトの復元に成功しました。" @@ -6753,18 +6798,3 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "コミュニティ版" - -#~ msgid "Classic green" -#~ msgstr "クラシックグリーン" - -#~ msgid "Chinese red" -#~ msgstr "チャイナレッド" - -#~ msgid "Tech blue" -#~ msgstr "テクノロジーブルー" - -#~ msgid "Deep black" -#~ msgstr "深き黒" - -#~ msgid "Filters" -#~ msgstr "フィルター" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index bb56392af..951c7182d 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:603bfc1d89caaec72caf5cfba85fdc2a1ae7ac9556b4c3641353153d777ece1b -size 104603 +oid sha256:393289b7bc2842cf9190af0783281a8e8acdb636b0ff4e88ef4ff931c2c11f5c +size 105290 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ceae873f1..89123d879 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-11 17:46+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:20 #: 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,7 +57,7 @@ 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 @@ -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:53 authentication/models.py:77 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:65 authentication/models.py:89 #: 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:244 #: 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:66 authentication/models.py:94 #: 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:82 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,7 +302,7 @@ 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:69 perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:58 terminal/models/storage.py:142 #: tickets/models/comment.py:26 tickets/models/flow.py:57 @@ -312,7 +314,7 @@ 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:98 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" @@ -579,7 +582,7 @@ msgid "Nodes" msgstr "节点" #: assets/models/asset.py:219 assets/models/cmd_filter.py:47 -#: assets/models/domain.py:65 assets/models/label.py:22 +#: assets/models/domain.py:66 assets/models/label.py:22 #: users/serializers/user.py:145 msgid "Is active" msgstr "激活" @@ -758,7 +761,7 @@ msgstr "失败" msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:40 authentication/models.py:72 +#: assets/models/base.py:40 authentication/models.py:247 msgid "Date verified" msgstr "校验日期" @@ -892,24 +895,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 "连接失败" @@ -1025,7 +1028,7 @@ msgid "SFTP Root" msgstr "SFTP根路径" #: assets/models/user.py:258 assets/serializers/system_user.py:37 -#: authentication/models.py:49 +#: authentication/models.py:51 msgid "Token" msgstr "Token" @@ -1074,7 +1077,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:86 msgid "System user display" msgstr "系统用户名称" @@ -1564,7 +1567,8 @@ msgstr "运行用户" msgid "Run as display" msgstr "运行用户名称" -#: audits/serializers.py:102 rbac/serializers/rolebinding.py:21 +#: audits/serializers.py:102 authentication/models.py:80 +#: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "用户名称" @@ -1592,7 +1596,7 @@ msgstr "企业微信" msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:55 authentication/models.py:76 +#: audits/signal_handlers.py:55 authentication/models.py:251 msgid "Temporary token" msgstr "临时密码" @@ -1768,10 +1772,6 @@ msgstr "{ApplicationPermission} 移除 {SystemUser}" 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 +2061,87 @@ msgstr "该 MFA ({}) 方式没有启用" msgid "Please change your password" msgstr "请修改密码" -#: authentication/models.py:34 +#: authentication/models.py:36 msgid "Access key" msgstr "Access key" -#: authentication/models.py:41 +#: authentication/models.py:43 msgid "Private Token" msgstr "SSH密钥" -#: authentication/models.py:50 +#: authentication/models.py:52 msgid "Expired" msgstr "过期时间" -#: authentication/models.py:54 +#: authentication/models.py:56 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:71 authentication/models.py:245 #: 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:73 authentication/models.py:248 +#: 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 +msgid "Asset display" +msgstr "资产名称" + +#: authentication/models.py:103 +msgid "Connection token" +msgstr "连接令牌" + +#: authentication/models.py:105 +msgid "Can view connection token secret" +msgstr "可以查看连接令牌密文" + +#: authentication/models.py:140 +msgid "Connection token expired at: {}" +msgstr "连接令牌过期: {}" + +#: authentication/models.py:145 +msgid "User not exists" +msgstr "用户不存在" + +#: authentication/models.py:149 +msgid "User invalid, disabled or expired" +msgstr "用户无效,已禁用或已过期" + +#: authentication/models.py:154 +msgid "System user not exists" +msgstr "系统用户不存在" + +#: authentication/models.py:160 +msgid "Asset not exists" +msgstr "资产不存在" + +#: authentication/models.py:164 +msgid "Asset inactive" +msgstr "资产未激活" + +#: authentication/models.py:171 +msgid "User has no permission to access asset or permission expired" +msgstr "用户没有权限访问资产或权限已过期" + +#: authentication/models.py:179 +msgid "Application not exists" +msgstr "应用不存在" + +#: authentication/models.py:186 +msgid "User has no permission to access application or permission expired" +msgstr "用户没有权限访问应用或权限已过期" + +#: authentication/models.py:246 +msgid "Verified" +msgstr "已校验" + +#: authentication/models.py:267 msgid "Super connection token" msgstr "超级连接令牌" @@ -2113,6 +2153,15 @@ 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 @@ -6236,10 +6285,6 @@ msgstr "云管中心" msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:34 -msgid "Validity" -msgstr "有效" - #: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "云账号" @@ -6594,11 +6639,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 "恢复默认成功!" @@ -6661,21 +6702,3 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 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 "验证码无效: {}" 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/terminal/api/endpoint.py b/apps/terminal/api/endpoint.py index aaece2222..5e4c7542d 100644 --- a/apps/terminal/api/endpoint.py +++ b/apps/terminal/api/endpoint.py @@ -54,15 +54,15 @@ class SmartEndpointViewMixin: asset_id = request.GET.get('asset_id') app_id = request.GET.get('app_id') session_id = request.GET.get('session_id') - token = request.GET.get('token') - if token: - from authentication.api.connection_token import TokenCacheMixin as TokenUtil - value = TokenUtil().get_token_from_cache(token) - if value: - if value.get('type') == 'asset': - asset_id = value.get('asset') - else: - app_id = value.get('application') + token_id = request.GET.get('token') + if token_id: + from authentication.models import ConnectionToken + token = ConnectionToken.objects.filter(id=token_id).first() + if token: + if token.asset: + asset_id = token.asset.id + elif token.application: + app_id = token.application.id if asset_id: pk, model = asset_id, Asset elif app_id: diff --git a/apps/users/urls/api_urls.py b/apps/users/urls/api_urls.py index 8453ec819..284482a63 100644 --- a/apps/users/urls/api_urls.py +++ b/apps/users/urls/api_urls.py @@ -16,7 +16,7 @@ router.register(r'users', api.UserViewSet, 'user') router.register(r'groups', api.UserGroupViewSet, 'user-group') router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'users-groups-relation') router.register(r'service-account-registrations', api.ServiceAccountRegistrationViewSet, 'service-account-registration') -router.register(r'connection-token', auth_api.UserConnectionTokenViewSet, 'connection-token') +router.register(r'connection-token', auth_api.ConnectionTokenViewSet, 'connection-token') urlpatterns = [