refactor: 重构 Connection Token 模块 (完成获取 Super connection token API 逻辑) (#8559)

* refactor: 重构 Connection Token 模块 (完成 Model 设计和创建 Token 的API逻辑)

* refactor: 重构 Connection Token 模块 (完成获取 Token 详细信息的 API 逻辑)

* refactor: 重构 Connection Token 模块 (完成获取 RDP 文件 API 逻辑)

* refactor: 重构 Connection Token 模块 (完成获取 Client url API 逻辑)

* refactor: 重构 Connection Token 模块 (完成获取 Super connection token API 逻辑)

* refactor: 重构 Connection Token 模块 (完成删除原 Connection token 逻辑)

* refactor: 重构 Connection Token 模块 (完成删除原 Connection)

* refactor: 重构 Connection Token 模块 (完善序列类字段)

* refactor: 重构 Connection Token 模块 (完善expire API)

* refactor: 重构 Connection Token 模块 (完善迁移文件)

* refactor: 重构 Connection Token 模块 (完善翻译文件)

* refactor: 重构 Connection Token 模块 (拆分Connection ViewSet)

* refactor: 重构 Connection Token 模块 (修改翻译)

* refactor: 重构 Connection Token 模块 (优化)

Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>
pull/8565/head
fit2bot 2022-07-11 18:09:06 +08:00 committed by GitHub
parent 7047e445a3
commit 27cbbfbc79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 872 additions and 757 deletions

View File

@ -9,7 +9,7 @@ import paramiko
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ 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 orgs.mixins.models import OrgModelMixin
from .base import BaseUser from .base import BaseUser
@ -36,7 +36,7 @@ class Domain(OrgModelMixin):
def has_gateway(self): def has_gateway(self):
return self.gateway_set.filter(is_active=True).exists() return self.gateway_set.filter(is_active=True).exists()
@property @lazyproperty
def gateways(self): def gateways(self):
return self.gateway_set.filter(is_active=True) 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] gateways = [gw for gw in self.gateways if gw.is_connective]
if gateways: if gateways:
return random.choice(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) return random.choice(self.gateways)

View File

@ -1,58 +1,60 @@
# -*- coding: utf-8 -*-
#
import urllib.parse
import json
from typing import Callable
import os import os
import json
import base64 import base64
import ctypes import urllib.parse
from django.core.cache import cache
from django.shortcuts import get_object_or_404
from django.http import HttpResponse from django.http import HttpResponse
from django.utils import timezone from django.shortcuts import get_object_or_404
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 rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from rest_framework import serializers from rest_framework.decorators import action
from django.conf import settings from rest_framework.response import Response
from rest_framework import status
from rest_framework.request import Request
from applications.models import Application from common.drf.api import JMSModelViewSet
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.http import is_true from common.http import is_true
from orgs.mixins.api import RootOrgViewMixin
from perms.models.base import Action 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 terminal.models import EndpointRule
from ..serializers import ( from ..serializers import (
ConnectionTokenSerializer, ConnectionTokenSecretSerializer, SuperConnectionTokenSerializer ConnectionTokenSerializer, ConnectionTokenSecretSerializer, SuperConnectionTokenSerializer
) )
from ..models import ConnectionToken
logger = get_logger(__name__)
__all__ = ['UserConnectionTokenViewSet', 'UserSuperConnectionTokenViewSet', 'TokenCacheMixin']
class ClientProtocolMixin: __all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet']
"""
下载客户端支持的连接文件里面包含了 token 其他连接信息
- [x] RDP
- [ ] KoKo
本质上这里还是暴露出 token 进行使用 class ConnectionTokenMixin:
"""
request: Request request: Request
get_serializer: Callable
create_token: Callable @staticmethod
get_serializer_context: Callable 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): def get_smart_endpoint(self, protocol, asset=None, application=None):
if asset: if asset:
@ -64,21 +66,32 @@ class ClientProtocolMixin:
endpoint = EndpointRule.match_endpoint(target_ip, protocol, self.request) endpoint = EndpointRule.match_endpoint(target_ip, protocol, self.request)
return endpoint 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 @staticmethod
def parse_env_bool(env_key, env_default, true_value, false_value): 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 return true_value if is_true(os.getenv(env_key, env_default)) else false_value
def get_rdp_file_content(self, serializer): def get_client_protocol_data(self, token: ConnectionToken):
options = { 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': '', 'full address:s': '',
'username:s': '', 'username:s': '',
# 'screen mode id:i': '1', # 'screen mode id:i': '1',
@ -111,412 +124,162 @@ class ClientProtocolMixin:
# 'remoteapplicationcmdline:s': '', # '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') height = self.request.query_params.get('height')
width = self.request.query_params.get('width') 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: if width and height:
options['desktopwidth:i'] = width rdp_options['desktopwidth:i'] = width
options['desktopheight:i'] = height rdp_options['desktopheight:i'] = height
options['winposstr:s:'] = f'0,1,0,0,{width},{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: if token.asset:
name = asset.hostname name = token.asset.hostname
elif application: elif token.application and token.application.category_remote_app:
name = application.name app = '||jmservisor'
application.get_rdp_remote_app_setting() name = token.application.name
rdp_options['remoteapplicationmode:i'] = '1'
app = f'||jmservisor' rdp_options['alternate shell:s'] = app
options['remoteapplicationmode:i'] = '1' rdp_options['remoteapplicationprogram:s'] = app
options['alternate shell:s'] = app rdp_options['remoteapplicationname:s'] = name
options['remoteapplicationprogram:s'] = app
options['remoteapplicationname:s'] = name
else: else:
name = '*' name = '*'
filename = "{}-{}-jumpserver".format(token.user.username, name)
filename = urllib.parse.quote(filename)
content = '' content = ''
for k, v in options.items(): for k, v in rdp_options.items():
content += f'{k}:{v}\n' content += f'{k}:{v}\n'
return name, content
def get_ssh_token(self, serializer): return filename, content
asset, application, system_user, user = self.get_request_resource(serializer)
token, secret = self.create_token(user, asset, application, system_user) def get_ssh_token(self, token: ConnectionToken):
if asset: if token.asset:
name = asset.hostname name = token.asset.hostname
elif application: elif token.application:
name = application.name name = token.application.name
else: else:
name = '*' name = '*'
filename = f'{token.user.username}-{name}-jumpserver'
endpoint = self.get_smart_endpoint( endpoint = self.get_smart_endpoint(
protocol='ssh', asset=asset, application=application protocol='ssh', asset=token.asset, application=token.application
) )
content = { data = {
'ip': endpoint.host, 'ip': endpoint.host,
'port': str(endpoint.ssh_port), 'port': str(endpoint.ssh_port),
'username': f'JMS-{token}', 'username': 'JMS-{}'.format(str(token.id)),
'password': secret 'password': token.secret
} }
token = json.dumps(content) token = json.dumps(data)
return name, token 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
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)
class SecretDetailMixin: class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelViewSet):
valid_token: Callable filterset_fields = (
request: Request 'type',
get_serializer: Callable 'user_display', 'system_user_display', 'application_display', 'asset_display'
@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 search_fields = filterset_fields
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):
serializer_classes = { serializer_classes = {
'default': ConnectionTokenSerializer, 'default': ConnectionTokenSerializer,
'get_secret_detail': ConnectionTokenSecretSerializer, 'get_secret_detail': ConnectionTokenSecretSerializer,
} }
rbac_perms = { rbac_perms = {
'GET': 'authentication.view_connectiontoken', 'retrieve': 'authentication.view_connectiontoken',
'create': 'authentication.add_connectiontoken', 'create': 'authentication.add_connectiontoken',
'expire': 'authentication.add_connectiontoken',
'get_secret_detail': 'authentication.view_connectiontokensecret', 'get_secret_detail': 'authentication.view_connectiontokensecret',
'get_rdp_file': 'authentication.add_connectiontoken', 'get_rdp_file': 'authentication.add_connectiontoken',
'get_client_protocol_url': 'authentication.add_connectiontoken', 'get_client_protocol_url': 'authentication.add_connectiontoken',
} }
queryset = ConnectionToken.objects.all()
def valid_token(self, token): def create_connection_token(self):
from users.models import User data = self.request.query_params if self.request.method == 'GET' else self.request.data
from assets.models import SystemUser, Asset serializer = self.get_serializer(data=data)
from applications.models import Application serializer.is_valid(raise_exception=True)
from perms.utils.asset.permission import validate_permission as asset_validate_permission self.perform_create(serializer)
from perms.utils.application.permission import validate_permission as app_validate_permission token: ConnectionToken = serializer.instance
return token
value = self.get_token_from_cache(token) def perform_create(self, serializer):
if not value: user, asset, application, system_user = self.get_request_resources(serializer)
raise serializers.ValidationError('Token not found') 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')) @action(methods=['POST'], detail=False, url_path='secret-info/detail')
if not user.is_valid: def get_secret_detail(self, request, *args, **kwargs):
raise serializers.ValidationError("User not valid, disabled or expired") # 非常重要的 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')) @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file')
asset = None def get_rdp_file(self, request, *args, **kwargs):
app = None token = self.create_connection_token()
if value.get('type') == 'asset': self.check_token_valid(token)
asset = get_object_or_404(Asset, id=value.get('asset')) filename, content = self.get_rdp_file_info(token)
if not asset.is_active: filename = '{}.rdp'.format(filename)
raise serializers.ValidationError("Asset disabled") response = HttpResponse(content, content_type='application/octet-stream')
has_perm, actions, expired_at = asset_validate_permission(user, asset, system_user) response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % filename
else: return response
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: @action(methods=['POST', 'GET'], detail=False, url_path='client-url')
raise serializers.ValidationError('Permission expired or invalid') def get_client_protocol_url(self, request, *args, **kwargs):
return value, user, system_user, asset, app, expired_at, actions 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): @action(methods=['PATCH'], detail=True)
token = request.query_params.get('token') def expire(self, request, *args, **kwargs):
value = self.get_token_from_cache(token) instance = self.get_object()
if not value: instance.expire()
return Response('', status=404) return Response(status=status.HTTP_204_NO_CONTENT)
return Response(value)
class UserSuperConnectionTokenViewSet( class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
BaseUserConnectionTokenViewSet, TokenCacheMixin, GenericViewSet
):
serializer_classes = { serializer_classes = {
'default': SuperConnectionTokenSerializer, 'default': SuperConnectionTokenSerializer,
} }
@ -525,10 +288,19 @@ class UserSuperConnectionTokenViewSet(
'renewal': 'authentication.add_superconnectiontoken' 'renewal': 'authentication.add_superconnectiontoken'
} }
@action(methods=[PATCH], detail=False) @action(methods=['PATCH'], detail=False)
def renewal(self, request, *args, **kwargs): def renewal(self, request, *args, **kwargs):
""" 续期 Token """ from common.utils.timezone import as_current_tz
token = request.data.get('token', '')
data = self.renewal_token(token) token_id = request.data.get('token') or ''
status_code = 200 if data.get('ok') else 404 token = get_object_or_404(ConnectionToken, pk=token_id)
return Response(data=data, status=status_code) 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)

View File

@ -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'},
),
]

View File

@ -1,11 +1,14 @@
import uuid import uuid
from datetime import datetime, timedelta
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from orgs.mixins.models import OrgModelMixin
from common.db import models from common.db import models
from common.utils import lazyproperty
from common.utils.timezone import as_current_tz
class AccessKey(models.Model): class AccessKey(models.Model):
@ -54,16 +57,189 @@ class SSOToken(models.JMSBaseModel):
verbose_name = _('SSO token') verbose_name = _('SSO token')
class ConnectionToken(models.JMSBaseModel): def date_expired_default():
# Todo: 未来可能放到这里,不记录到 redis 了,虽然方便,但是不易于审计 return timezone.now() + timedelta(seconds=settings.CONNECTION_TOKEN_EXPIRATION)
# Todo: add connection token 可能要授权给 普通用户, 或者放开就行
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: class Meta:
ordering = ('-date_expired',)
verbose_name = _('Connection token') verbose_name = _('Connection token')
permissions = [ permissions = [
('view_connectiontokensecret', _('Can view connection token secret')) ('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): class TempToken(models.JMSModel):
username = models.CharField(max_length=128, verbose_name=_("Username")) username = models.CharField(max_length=128, verbose_name=_("Username"))

View File

@ -1,4 +1,4 @@
from .token import * from .token import *
from .connect_token import * from .connection_token import *
from .password_mfa import * from .password_mfa import *
from .confirm import * from .confirm import *

View File

@ -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()

View File

@ -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',
]

View File

@ -10,8 +10,8 @@ router = DefaultRouter()
router.register('access-keys', api.AccessKeyViewSet, 'access-key') router.register('access-keys', api.AccessKeyViewSet, 'access-key')
router.register('sso', api.SSOViewSet, 'sso') router.register('sso', api.SSOViewSet, 'sso')
router.register('temp-tokens', api.TempTokenViewSet, 'temp-token') router.register('temp-tokens', api.TempTokenViewSet, 'temp-token')
router.register('connection-token', api.UserConnectionTokenViewSet, 'connection-token') router.register('connection-token', api.ConnectionTokenViewSet, 'connection-token')
router.register('super-connection-token', api.UserSuperConnectionTokenViewSet, 'super-connection-token') router.register('super-connection-token', api.SuperConnectionTokenViewSet, 'super-connection-token')
urlpatterns = [ urlpatterns = [

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:404cc258252754b1fbbb67b04b326369c7ca0797d5245fdd98939c2206e1d8a3 oid sha256:9abfbe0feec22996ffa184b7865f99d1357bad8c022bfba5b28c3f5bfbd0783f
size 126727 size 127716

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -47,7 +47,7 @@ msgstr "優先順位"
msgid "1-100, the lower the value will be match first" msgid "1-100, the lower the value will be match first"
msgstr "1-100、低い値は最初に一致します" 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 #: authentication/templates/authentication/_access_key_modal.html:32
#: perms/models/base.py:88 terminal/models/sharing.py:28 tickets/const.py:39 #: perms/models/base.py:88 terminal/models/sharing.py:28 tickets/const.py:39
msgid "Active" msgid "Active"
@ -58,7 +58,7 @@ msgstr "アクティブ"
#: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/backup.py:54 assets/models/base.py:180
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:48
#: assets/models/cmd_filter.py:96 assets/models/domain.py:24 #: 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 #: 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 #: 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/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 #: 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 #: 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 #: 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 #: authentication/models.py:53 authentication/models.py:77 orgs/models.py:214
#: rbac/builtin.py:118 rbac/models/rolebinding.py:41 #: perms/models/base.py:84 rbac/builtin.py:118 rbac/models/rolebinding.py:41
#: terminal/backends/command/models.py:20 #: terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:44
#: terminal/models/sharing.py:33 terminal/notifications.py:91 #: 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/backup.py:31 assets/models/cmd_filter.py:38
#: assets/models/gathered_user.py:14 assets/serializers/label.py:30 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30
#: assets/serializers/system_user.py:268 audits/models.py:39 #: 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 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21
#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:46
#: terminal/notifications.py:90 #: terminal/notifications.py:90
@ -156,7 +157,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176
#: assets/models/gathered_user.py:15 audits/models.py:121 #: assets/models/gathered_user.py:15 audits/models.py:121
#: authentication/forms.py:25 authentication/forms.py:27 #: 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_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.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 #: 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 #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33
#: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: 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 #: assets/serializers/account.py:13
#: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8 #: 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 #: 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 #: terminal/serializers/session.py:31 terminal/serializers/storage.py:68
msgid "Protocol" msgid "Protocol"
msgstr "プロトコル" msgstr "プロトコル"
@ -261,13 +262,14 @@ msgstr "カスタム"
#: applications/models/account.py:12 applications/models/application.py:234 #: applications/models/account.py:12 applications/models/application.py:234
#: assets/models/backup.py:32 assets/models/cmd_filter.py:45 #: 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 #: perms/models/application_permission.py:28
msgid "Application" msgid "Application"
msgstr "アプリケーション" msgstr "アプリケーション"
#: applications/models/account.py:15 assets/models/authbook.py:20 #: 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 #: 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 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22
#: terminal/backends/command/serializers.py:36 terminal/models/session.py:48 #: terminal/backends/command/serializers.py:36 terminal/models/session.py:48
#: xpack/plugins/change_auth_plan/models/app.py:36 #: xpack/plugins/change_auth_plan/models/app.py:36
@ -305,7 +307,7 @@ msgstr "カテゴリ"
#: applications/models/application.py:222 #: applications/models/application.py:222
#: applications/serializers/application.py:101 assets/models/backup.py:49 #: applications/serializers/application.py:101 assets/models/backup.py:49
#: assets/models/cmd_filter.py:82 assets/models/user.py:250 #: 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 #: 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:142
#: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/comment.py:26 tickets/models/flow.py:57
@ -317,7 +319,7 @@ msgid "Type"
msgstr "タイプ" msgstr "タイプ"
#: applications/models/application.py:226 assets/models/asset.py:217 #: 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" msgid "Domain"
msgstr "ドメイン" msgstr "ドメイン"
@ -343,7 +345,8 @@ msgstr "カテゴリ表示"
#: applications/serializers/application.py:71 #: applications/serializers/application.py:71
#: applications/serializers/application.py:102 #: applications/serializers/application.py:102
#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:34 #: 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 #: tickets/serializers/flow.py:48 tickets/serializers/ticket/ticket.py:17
msgid "Type display" msgid "Type display"
msgstr "タイプ表示" msgstr "タイプ表示"
@ -370,7 +373,7 @@ msgid "Date updated"
msgstr "更新日" msgstr "更新日"
#: applications/serializers/application.py:121 #: applications/serializers/application.py:121
#: applications/serializers/application.py:166 #: applications/serializers/application.py:166 authentication/models.py:98
msgid "Application display" msgid "Application display"
msgstr "アプリケーション表示" msgstr "アプリケーション表示"
@ -398,7 +401,7 @@ msgstr "ホスト"
#: applications/serializers/attrs/application_type/pgsql.py:10 #: applications/serializers/attrs/application_type/pgsql.py:10
#: applications/serializers/attrs/application_type/redis.py:10 #: applications/serializers/attrs/application_type/redis.py:10
#: applications/serializers/attrs/application_type/sqlserver.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 #: settings/serializers/auth/radius.py:15
#: xpack/plugins/cloud/serializers/account_attrs.py:71 #: xpack/plugins/cloud/serializers/account_attrs.py:71
msgid "Port" msgid "Port"
@ -584,7 +587,7 @@ msgid "Nodes"
msgstr "ノード" msgstr "ノード"
#: assets/models/asset.py:219 assets/models/cmd_filter.py:47 #: 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 #: users/serializers/user.py:145
msgid "Is active" msgid "Is active"
msgstr "アクティブです。" msgstr "アクティブです。"
@ -763,7 +766,7 @@ msgstr "失敗しました"
msgid "Connectivity" msgid "Connectivity"
msgstr "接続性" msgstr "接続性"
#: assets/models/base.py:40 authentication/models.py:72 #: assets/models/base.py:40 authentication/models.py:247
msgid "Date verified" msgid "Date verified"
msgstr "確認済みの日付" msgstr "確認済みの日付"
@ -897,24 +900,24 @@ msgstr "生成された正規表現が正しくありません: {}"
msgid "Command confirm" msgid "Command confirm"
msgstr "コマンドの確認" msgstr "コマンドの確認"
#: assets/models/domain.py:72 #: assets/models/domain.py:73
msgid "Gateway" msgid "Gateway"
msgstr "ゲートウェイ" msgstr "ゲートウェイ"
#: assets/models/domain.py:74 #: assets/models/domain.py:75
msgid "Test gateway" msgid "Test gateway"
msgstr "テストゲートウェイ" msgstr "テストゲートウェイ"
#: assets/models/domain.py:130 #: assets/models/domain.py:131
#, python-brace-format #, python-brace-format
msgid "Unable to connect to port {port} on {ip}" msgid "Unable to connect to port {port} on {ip}"
msgstr "{ip} でポート {port} に接続できません" 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" msgid "Authentication failed"
msgstr "認証に失敗しました" msgstr "認証に失敗しました"
#: assets/models/domain.py:135 assets/models/domain.py:157 #: assets/models/domain.py:136 assets/models/domain.py:158
msgid "Connect failed" msgid "Connect failed"
msgstr "接続に失敗しました" msgstr "接続に失敗しました"
@ -1030,7 +1033,7 @@ msgid "SFTP Root"
msgstr "SFTPルート" msgstr "SFTPルート"
#: assets/models/user.py:258 assets/serializers/system_user.py:37 #: assets/models/user.py:258 assets/serializers/system_user.py:37
#: authentication/models.py:49 #: authentication/models.py:51
msgid "Token" msgid "Token"
msgstr "トークン" msgstr "トークン"
@ -1082,7 +1085,7 @@ msgstr ""
"定してください暗号化パスワード" "定してください暗号化パスワード"
#: assets/serializers/account.py:36 assets/serializers/account.py:87 #: 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" msgid "System user display"
msgstr "システムユーザー表示" msgstr "システムユーザー表示"
@ -1576,7 +1579,8 @@ msgstr "として実行"
msgid "Run as display" msgid "Run as display"
msgstr "ディスプレイとして実行する" 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" msgid "User display"
msgstr "ユーザー表示" msgstr "ユーザー表示"
@ -1604,7 +1608,7 @@ msgstr "企業微信"
msgid "DingTalk" msgid "DingTalk"
msgstr "DingTalk" msgstr "DingTalk"
#: audits/signal_handlers.py:55 authentication/models.py:76 #: audits/signal_handlers.py:55 authentication/models.py:251
msgid "Temporary token" msgid "Temporary token"
msgstr "仮パスワード" msgstr "仮パスワード"
@ -1780,10 +1784,6 @@ msgstr "{ApplicationPermission} 削除 {SystemUser}"
msgid "This action require verify your MFA" msgid "This action require verify your MFA"
msgstr "この操作には、MFAを検証する必要があります" msgstr "この操作には、MFAを検証する必要があります"
#: authentication/api/connection_token.py:326
msgid "Invalid token"
msgstr "無効なトークン"
#: authentication/api/mfa.py:59 #: authentication/api/mfa.py:59
msgid "Current user not support mfa type: {}" msgid "Current user not support mfa type: {}"
msgstr "現在のユーザーはmfaタイプをサポートしていません: {}" msgstr "現在のユーザーはmfaタイプをサポートしていません: {}"
@ -2082,47 +2082,91 @@ msgstr "MFAタイプ ({}) が有効になっていない"
msgid "Please change your password" msgid "Please change your password"
msgstr "パスワードを変更してください" msgstr "パスワードを変更してください"
#: authentication/models.py:34 #: authentication/models.py:36
msgid "Access key" msgid "Access key"
msgstr "アクセスキー" msgstr "アクセスキー"
#: authentication/models.py:41 #: authentication/models.py:43
msgid "Private Token" msgid "Private Token"
msgstr "プライベートトークン" msgstr "プライベートトークン"
#: authentication/models.py:50 #: authentication/models.py:52
msgid "Expired" msgid "Expired"
msgstr "期限切れ" msgstr "期限切れ"
#: authentication/models.py:54 #: authentication/models.py:56
msgid "SSO token" msgid "SSO token"
msgstr "SSO token" msgstr "SSO token"
#: authentication/models.py:62 #: authentication/models.py:71 authentication/models.py:245
msgid "Connection token"
msgstr "接続トークン"
#: authentication/models.py:64
msgid "Can view connection token secret"
msgstr "接続トークンの秘密を表示できます"
#: authentication/models.py:70
#: authentication/templates/authentication/_access_key_modal.html:31 #: authentication/templates/authentication/_access_key_modal.html:31
#: settings/serializers/auth/radius.py:17 #: settings/serializers/auth/radius.py:17
msgid "Secret" msgid "Secret"
msgstr "ひみつ" msgstr "ひみつ"
#: authentication/models.py:71 #: authentication/models.py:73 authentication/models.py:248
msgid "Verified" #: perms/models/base.py:90 tickets/models/ticket/apply_application.py:26
msgstr "確認済み"
#: authentication/models.py:73 perms/models/base.py:90
#: tickets/models/ticket/apply_application.py:26
#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:703 #: tickets/models/ticket/apply_asset.py:24 users/models/user.py:703
msgid "Date expired" msgid "Date expired"
msgstr "期限切れの日付" msgstr "期限切れの日付"
#: authentication/models.py:92 #: 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" msgid "Super connection token"
msgstr "スーパー接続トークン" msgstr "スーパー接続トークン"
@ -2134,6 +2178,15 @@ msgstr "異なる都市ログインのリマインダー"
msgid "binding reminder" msgid "binding reminder"
msgstr "バインディングリマインダー" 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 #: authentication/serializers/token.py:79
#: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:20
#: perms/serializers/application/permission.py:41 #: perms/serializers/application/permission.py:41
@ -6327,10 +6380,6 @@ msgstr "クラウドセンター"
msgid "Provider" msgid "Provider"
msgstr "プロバイダー" msgstr "プロバイダー"
#: xpack/plugins/cloud/models.py:34
msgid "Validity"
msgstr "有効性"
#: xpack/plugins/cloud/models.py:39 #: xpack/plugins/cloud/models.py:39
msgid "Cloud account" msgid "Cloud account"
msgstr "クラウドアカウント" msgstr "クラウドアカウント"
@ -6686,11 +6735,7 @@ msgstr "資産は空です。ノードを変更してください"
msgid "Executed times" msgid "Executed times"
msgstr "実行時間" msgstr "実行時間"
#: xpack/plugins/interface/api.py:50 #: xpack/plugins/interface/api.py:52
msgid "It is already in the default setting state!"
msgstr "すでにデフォルトの設定状態です!"
#: xpack/plugins/interface/api.py:53
msgid "Restore default successfully." msgid "Restore default successfully."
msgstr "デフォルトの復元に成功しました。" msgstr "デフォルトの復元に成功しました。"
@ -6753,18 +6798,3 @@ msgstr "究極のエディション"
#: xpack/plugins/license/models.py:77 #: xpack/plugins/license/models.py:77
msgid "Community edition" msgid "Community edition"
msgstr "コミュニティ版" msgstr "コミュニティ版"
#~ msgid "Classic green"
#~ msgstr "クラシックグリーン"
#~ msgid "Chinese red"
#~ msgstr "チャイナレッド"
#~ msgid "Tech blue"
#~ msgstr "テクノロジーブルー"
#~ msgid "Deep black"
#~ msgstr "深き黒"
#~ msgid "Filters"
#~ msgstr "フィルター"

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:603bfc1d89caaec72caf5cfba85fdc2a1ae7ac9556b4c3641353153d777ece1b oid sha256:393289b7bc2842cf9190af0783281a8e8acdb636b0ff4e88ef4ff931c2c11f5c
size 104603 size 105290

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -46,7 +46,7 @@ msgstr "优先级"
msgid "1-100, the lower the value will be match first" msgid "1-100, the lower the value will be match first"
msgstr "优先级可选范围为 1-100 (数值越小越优先)" 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 #: authentication/templates/authentication/_access_key_modal.html:32
#: perms/models/base.py:88 terminal/models/sharing.py:28 tickets/const.py:39 #: perms/models/base.py:88 terminal/models/sharing.py:28 tickets/const.py:39
msgid "Active" msgid "Active"
@ -57,7 +57,7 @@ msgstr "激活中"
#: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/backup.py:54 assets/models/base.py:180
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:48
#: assets/models/cmd_filter.py:96 assets/models/domain.py:24 #: 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 #: 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 #: 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/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 #: 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 #: 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 #: 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 #: authentication/models.py:53 authentication/models.py:77 orgs/models.py:214
#: rbac/builtin.py:118 rbac/models/rolebinding.py:41 #: perms/models/base.py:84 rbac/builtin.py:118 rbac/models/rolebinding.py:41
#: terminal/backends/command/models.py:20 #: terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:44
#: terminal/models/sharing.py:33 terminal/notifications.py:91 #: 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/backup.py:31 assets/models/cmd_filter.py:38
#: assets/models/gathered_user.py:14 assets/serializers/label.py:30 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30
#: assets/serializers/system_user.py:268 audits/models.py:39 #: 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 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21
#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:46
#: terminal/notifications.py:90 #: terminal/notifications.py:90
@ -155,7 +156,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176
#: assets/models/gathered_user.py:15 audits/models.py:121 #: assets/models/gathered_user.py:15 audits/models.py:121
#: authentication/forms.py:25 authentication/forms.py:27 #: 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_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.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 #: 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 #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33
#: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: 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 #: assets/serializers/account.py:13
#: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_password_success.html:8
@ -199,7 +200,7 @@ msgid ""
msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}"
#: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 #: 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 #: terminal/serializers/session.py:31 terminal/serializers/storage.py:68
msgid "Protocol" msgid "Protocol"
msgstr "协议" msgstr "协议"
@ -256,13 +257,14 @@ msgstr "自定义"
#: applications/models/account.py:12 applications/models/application.py:234 #: applications/models/account.py:12 applications/models/application.py:234
#: assets/models/backup.py:32 assets/models/cmd_filter.py:45 #: 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 #: perms/models/application_permission.py:28
msgid "Application" msgid "Application"
msgstr "应用程序" msgstr "应用程序"
#: applications/models/account.py:15 assets/models/authbook.py:20 #: 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 #: 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 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22
#: terminal/backends/command/serializers.py:36 terminal/models/session.py:48 #: terminal/backends/command/serializers.py:36 terminal/models/session.py:48
#: xpack/plugins/change_auth_plan/models/app.py:36 #: xpack/plugins/change_auth_plan/models/app.py:36
@ -300,7 +302,7 @@ msgstr "类别"
#: applications/models/application.py:222 #: applications/models/application.py:222
#: applications/serializers/application.py:101 assets/models/backup.py:49 #: applications/serializers/application.py:101 assets/models/backup.py:49
#: assets/models/cmd_filter.py:82 assets/models/user.py:250 #: 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 #: 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:142
#: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/comment.py:26 tickets/models/flow.py:57
@ -312,7 +314,7 @@ msgid "Type"
msgstr "类型" msgstr "类型"
#: applications/models/application.py:226 assets/models/asset.py:217 #: 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" msgid "Domain"
msgstr "网域" msgstr "网域"
@ -338,7 +340,8 @@ msgstr "类别名称"
#: applications/serializers/application.py:71 #: applications/serializers/application.py:71
#: applications/serializers/application.py:102 #: applications/serializers/application.py:102
#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:34 #: 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 #: tickets/serializers/flow.py:48 tickets/serializers/ticket/ticket.py:17
msgid "Type display" msgid "Type display"
msgstr "类型名称" msgstr "类型名称"
@ -365,7 +368,7 @@ msgid "Date updated"
msgstr "更新日期" msgstr "更新日期"
#: applications/serializers/application.py:121 #: applications/serializers/application.py:121
#: applications/serializers/application.py:166 #: applications/serializers/application.py:166 authentication/models.py:98
msgid "Application display" msgid "Application display"
msgstr "应用名称" msgstr "应用名称"
@ -393,7 +396,7 @@ msgstr "主机"
#: applications/serializers/attrs/application_type/pgsql.py:10 #: applications/serializers/attrs/application_type/pgsql.py:10
#: applications/serializers/attrs/application_type/redis.py:10 #: applications/serializers/attrs/application_type/redis.py:10
#: applications/serializers/attrs/application_type/sqlserver.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 #: settings/serializers/auth/radius.py:15
#: xpack/plugins/cloud/serializers/account_attrs.py:71 #: xpack/plugins/cloud/serializers/account_attrs.py:71
msgid "Port" msgid "Port"
@ -579,7 +582,7 @@ msgid "Nodes"
msgstr "节点" msgstr "节点"
#: assets/models/asset.py:219 assets/models/cmd_filter.py:47 #: 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 #: users/serializers/user.py:145
msgid "Is active" msgid "Is active"
msgstr "激活" msgstr "激活"
@ -758,7 +761,7 @@ msgstr "失败"
msgid "Connectivity" msgid "Connectivity"
msgstr "可连接性" msgstr "可连接性"
#: assets/models/base.py:40 authentication/models.py:72 #: assets/models/base.py:40 authentication/models.py:247
msgid "Date verified" msgid "Date verified"
msgstr "校验日期" msgstr "校验日期"
@ -892,24 +895,24 @@ msgstr "生成的正则表达式有误"
msgid "Command confirm" msgid "Command confirm"
msgstr "命令复核" msgstr "命令复核"
#: assets/models/domain.py:72 #: assets/models/domain.py:73
msgid "Gateway" msgid "Gateway"
msgstr "网关" msgstr "网关"
#: assets/models/domain.py:74 #: assets/models/domain.py:75
msgid "Test gateway" msgid "Test gateway"
msgstr "测试网关" msgstr "测试网关"
#: assets/models/domain.py:130 #: assets/models/domain.py:131
#, python-brace-format #, python-brace-format
msgid "Unable to connect to port {port} on {ip}" msgid "Unable to connect to port {port} on {ip}"
msgstr "无法连接到 {ip} 上的端口 {port}" 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" msgid "Authentication failed"
msgstr "认证失败" msgstr "认证失败"
#: assets/models/domain.py:135 assets/models/domain.py:157 #: assets/models/domain.py:136 assets/models/domain.py:158
msgid "Connect failed" msgid "Connect failed"
msgstr "连接失败" msgstr "连接失败"
@ -1025,7 +1028,7 @@ msgid "SFTP Root"
msgstr "SFTP根路径" msgstr "SFTP根路径"
#: assets/models/user.py:258 assets/serializers/system_user.py:37 #: assets/models/user.py:258 assets/serializers/system_user.py:37
#: authentication/models.py:49 #: authentication/models.py:51
msgid "Token" msgid "Token"
msgstr "Token" msgstr "Token"
@ -1074,7 +1077,7 @@ msgstr ""
"置加密密码" "置加密密码"
#: assets/serializers/account.py:36 assets/serializers/account.py:87 #: 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" msgid "System user display"
msgstr "系统用户名称" msgstr "系统用户名称"
@ -1564,7 +1567,8 @@ msgstr "运行用户"
msgid "Run as display" msgid "Run as display"
msgstr "运行用户名称" 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" msgid "User display"
msgstr "用户名称" msgstr "用户名称"
@ -1592,7 +1596,7 @@ msgstr "企业微信"
msgid "DingTalk" msgid "DingTalk"
msgstr "钉钉" msgstr "钉钉"
#: audits/signal_handlers.py:55 authentication/models.py:76 #: audits/signal_handlers.py:55 authentication/models.py:251
msgid "Temporary token" msgid "Temporary token"
msgstr "临时密码" msgstr "临时密码"
@ -1768,10 +1772,6 @@ msgstr "{ApplicationPermission} 移除 {SystemUser}"
msgid "This action require verify your MFA" msgid "This action require verify your MFA"
msgstr "此操作需要验证您的 MFA" msgstr "此操作需要验证您的 MFA"
#: authentication/api/connection_token.py:326
msgid "Invalid token"
msgstr "无效的令牌"
#: authentication/api/mfa.py:59 #: authentication/api/mfa.py:59
msgid "Current user not support mfa type: {}" msgid "Current user not support mfa type: {}"
msgstr "当前用户不支持 MFA 类型: {}" msgstr "当前用户不支持 MFA 类型: {}"
@ -2061,47 +2061,87 @@ msgstr "该 MFA ({}) 方式没有启用"
msgid "Please change your password" msgid "Please change your password"
msgstr "请修改密码" msgstr "请修改密码"
#: authentication/models.py:34 #: authentication/models.py:36
msgid "Access key" msgid "Access key"
msgstr "Access key" msgstr "Access key"
#: authentication/models.py:41 #: authentication/models.py:43
msgid "Private Token" msgid "Private Token"
msgstr "SSH密钥" msgstr "SSH密钥"
#: authentication/models.py:50 #: authentication/models.py:52
msgid "Expired" msgid "Expired"
msgstr "过期时间" msgstr "过期时间"
#: authentication/models.py:54 #: authentication/models.py:56
msgid "SSO token" msgid "SSO token"
msgstr "SSO token" msgstr "SSO token"
#: authentication/models.py:62 #: authentication/models.py:71 authentication/models.py:245
msgid "Connection token"
msgstr "连接令牌"
#: authentication/models.py:64
msgid "Can view connection token secret"
msgstr "可以查看连接令牌密文"
#: authentication/models.py:70
#: authentication/templates/authentication/_access_key_modal.html:31 #: authentication/templates/authentication/_access_key_modal.html:31
#: settings/serializers/auth/radius.py:17 #: settings/serializers/auth/radius.py:17
msgid "Secret" msgid "Secret"
msgstr "密钥" msgstr "密钥"
#: authentication/models.py:71 #: authentication/models.py:73 authentication/models.py:248
msgid "Verified" #: perms/models/base.py:90 tickets/models/ticket/apply_application.py:26
msgstr "已校验"
#: authentication/models.py:73 perms/models/base.py:90
#: tickets/models/ticket/apply_application.py:26
#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:703 #: tickets/models/ticket/apply_asset.py:24 users/models/user.py:703
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
#: authentication/models.py:92 #: 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" msgid "Super connection token"
msgstr "超级连接令牌" msgstr "超级连接令牌"
@ -2113,6 +2153,15 @@ msgstr "异地登录提醒"
msgid "binding reminder" msgid "binding reminder"
msgstr "绑定提醒" 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 #: authentication/serializers/token.py:79
#: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:20
#: perms/serializers/application/permission.py:41 #: perms/serializers/application/permission.py:41
@ -6236,10 +6285,6 @@ msgstr "云管中心"
msgid "Provider" msgid "Provider"
msgstr "云服务商" msgstr "云服务商"
#: xpack/plugins/cloud/models.py:34
msgid "Validity"
msgstr "有效"
#: xpack/plugins/cloud/models.py:39 #: xpack/plugins/cloud/models.py:39
msgid "Cloud account" msgid "Cloud account"
msgstr "云账号" msgstr "云账号"
@ -6594,11 +6639,7 @@ msgstr "资产为空,请更改节点"
msgid "Executed times" msgid "Executed times"
msgstr "执行次数" msgstr "执行次数"
#: xpack/plugins/interface/api.py:50 #: xpack/plugins/interface/api.py:52
msgid "It is already in the default setting state!"
msgstr "当前已经是初始化状态!"
#: xpack/plugins/interface/api.py:53
msgid "Restore default successfully." msgid "Restore default successfully."
msgstr "恢复默认成功!" msgstr "恢复默认成功!"
@ -6661,21 +6702,3 @@ msgstr "旗舰版"
#: xpack/plugins/license/models.py:77 #: xpack/plugins/license/models.py:77
msgid "Community edition" msgid "Community edition"
msgstr "社区版" 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 "验证码无效: {}"

View File

@ -80,14 +80,3 @@ def get_application_system_user_ids(user, application):
def has_application_system_permission(user, application, system_user): def has_application_system_permission(user, application, system_user):
system_user_ids = get_application_system_user_ids(user, application) system_user_ids = get_application_system_user_ids(user, application)
return system_user.id in system_user_ids 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

View File

@ -109,9 +109,3 @@ def get_asset_system_user_ids_with_actions_by_group(group: UserGroup, asset: Ass
user_groups=group user_groups=group
).valid().values_list('id', flat=True).distinct() ).valid().values_list('id', flat=True).distinct()
return get_asset_system_user_ids_with_actions(asset_perm_ids, asset) 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

View File

@ -54,15 +54,15 @@ class SmartEndpointViewMixin:
asset_id = request.GET.get('asset_id') asset_id = request.GET.get('asset_id')
app_id = request.GET.get('app_id') app_id = request.GET.get('app_id')
session_id = request.GET.get('session_id') session_id = request.GET.get('session_id')
token = request.GET.get('token') 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:
from authentication.api.connection_token import TokenCacheMixin as TokenUtil if token.asset:
value = TokenUtil().get_token_from_cache(token) asset_id = token.asset.id
if value: elif token.application:
if value.get('type') == 'asset': app_id = token.application.id
asset_id = value.get('asset')
else:
app_id = value.get('application')
if asset_id: if asset_id:
pk, model = asset_id, Asset pk, model = asset_id, Asset
elif app_id: elif app_id:

View File

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