Compare commits

...

55 Commits

Author SHA1 Message Date
fit2bot
8f78acef13 feat: Update v2.28.16 2023-06-20 10:41:36 +08:00
老广
5da6a1b9b6 Merge pull request #10771 from jumpserver/pr@v2.28@fix_image
fix: 修正基础镜像名称
2023-06-19 16:22:11 +08:00
吴小白
988f33634e fix: 修正基础镜像名称 2023-06-19 16:20:43 +08:00
吴小白
a645dc09ae fix: 修正快速安装脚本版本参数 2023-06-13 14:22:10 +08:00
feng
4e4e58480f fix: authbook 没有的值取 systemuser 2023-06-08 15:36:19 +08:00
feng
4a1f3ed727 fix: 特权操作时 同时加载密钥密码 2023-06-08 10:20:20 +08:00
Bai
a10bb29a1e fix: 修复终端端点使用资产标签匹配机制时 500 的问题 2023-05-24 17:37:15 +08:00
老广
73aeb021cc Merge pull request #10490 from O-Jiangweidong/pr@v2.28@fix_mfa_bypass
fix: 修复某待审核用户返回时,登录其他用户可绕开mfa的问题
2023-05-19 10:40:52 +08:00
jiangweidong
3d66fe4758 fix: 缩进 2023-05-18 14:58:07 +08:00
jiangweidong
ec2071a6ca fix: 修复某待审核用户返回时,登录其他用户可绕开mfa的问题 2023-05-18 14:48:22 +08:00
fit2bot
beb43aa726 fix: 工单comment改成md格式 (#10419)
Co-authored-by: feng <1304903146@qq.com>
2023-05-10 15:19:31 +08:00
Bai
8a77a7b8b5 perf: 优化系统用户和资产,只有协议包含时才进行关联和推送 2023-05-08 19:07:59 +08:00
Bai
7eed182627 perf: 优化系统用户和资产,只有协议包含时才进行关联和推送 2023-05-08 18:14:19 +08:00
feng626
ec847d3ecb Fix v2.28.7 ssh key (#10399)
* feat: Update v2.28.7

* fix: 修复旧 ssh 私钥,解析失败的问题

* perf: 解决历史版本中因保存密码,造成 ssh 私钥解析失败问题

* fix: 动态用户可执行批量任务

---------

Co-authored-by: fit2bot <fit2bot@fit2cloud.com>
Co-authored-by: Eric <xplzv@126.com>
2023-05-08 14:28:23 +08:00
Eric
a0994e2e12 fix: 修复旧 ssh 私钥,解析失败的问题 2023-04-27 17:49:52 +08:00
ibuler
17e3ddda05 perf: 支持 rdp console 2023-04-25 14:35:28 +08:00
Bai
6e2e92be5e fix: perf: 修改OAuth2的access_token前缀格式 2023-04-20 14:59:54 +08:00
老广
e90d8c8561 Merge pull request #10121 from jumpserver/pr@v2.28@fix_ldapuserimport_v2.28
fix: 修复 LDAP 导入用户时指定其他组织,还会导入到 Default 组织的问题
2023-04-03 17:09:58 +08:00
Bai
cf972942fa fix: 修复 LDAP 导入用户时指定其他组织,还会导入到 Default 组织的问题 2023-04-03 16:54:14 +08:00
老广
72e35d5553 Merge pull request #10046 from jumpserver/pr@v2.28@fix_systemuserlist
fix: 去掉系统用户序列类中的资产数量和应用数量
2023-03-23 14:13:33 +08:00
Bai
0ba84e7e18 fix: 去掉系统用户序列类中的资产数量和应用数量 2023-03-23 11:52:18 +08:00
Bai
fbc5ae1b9b fix: 修复日志记录到syslog时中文编码问题 2023-03-15 19:45:40 +08:00
halo
2fcf045826 fix: 修复celery api 报错 2023-03-15 15:36:54 +08:00
Bai
32cba4f2a1 fix: 修复语言切换问题 2023-03-14 14:40:23 +08:00
Jiangjie.Bai
b76aa3b259 feat: 支持飞书国际版(lark) (#9916) 2023-03-10 15:49:15 +08:00
Eric
3f9a17347d fix: 修复存储故障造成的录像获取失败问题 2023-03-09 11:52:23 +08:00
老广
c01d1973d9 Merge pull request #9877 from jumpserver/pr@v2.28@fix_operatelog_hide_sth
fix: 操作日志显示用户加密后的密文,及日期格式调整
2023-03-08 14:05:20 +08:00
jiangweidong
b216a9abc0 fix: 操作日志显示用户加密后的密文,及日期格式调整 2023-03-08 11:36:48 +08:00
Bai
c628ba1c4b fix: 修复翻译 2023-03-02 12:25:29 +08:00
fit2bot
ebbae36c49 perf: k8s update api (#9833)
Co-authored-by: feng <1304903146@qq.com>
2023-03-02 11:03:29 +08:00
Bai
69ef25666e fix: 修复认证MFA失败次数清空问题 2023-02-24 14:44:32 +08:00
Bai
d0475397d0 fix: 修复第三方用户登录复核可以跳过的问题 2023-02-09 19:50:13 +08:00
老广
dad45e7ace Merge pull request #9233 from jumpserver/pr@v2.28@perf_dockerfile
build(deps): 优化龙芯构建依赖包
2022-12-22 12:43:37 +08:00
吴小白
720f9cd397 build(deps): 优化龙芯构建依赖包 2022-12-22 12:40:53 +08:00
Bai
81dee0c403 perf: 修改方法名称 check_db_port_mapper 2022-12-22 10:43:07 +08:00
老广
105ef791b8 Merge pull request #9230 from jumpserver/pr@v2.28@v2.28_perf_dbportmapper
fix: 修改db_port_mapper策略; 启动时进行check校验;
2022-12-21 18:59:47 +08:00
Bai
a19c0bde60 fix: 修改db_port_mapper策略; 启动时进行check校验; 2022-12-21 18:43:44 +08:00
halo
3996daf4a7 fix: 导入翻译引用 2022-12-16 11:47:39 +08:00
halo
ac235f788e perf: 优化oauth2的服务地址参数拼接 2022-12-16 11:47:39 +08:00
老广
67e334bf43 Merge pull request #9213 from jumpserver/pr@v2.28@perf_fingerprint
fix: 修复非 ssh 协议的系统用户存在错误私钥,引发的解析问题
2022-12-15 15:25:40 +08:00
Eric
f7f9fb1bdf fix: 修复非 ssh 协议的系统用户存在错误私钥,引发的解析问题 2022-12-15 15:17:36 +08:00
Eric
8979228e0b fix: 修复 ssh 私钥推送等问题 2022-12-13 16:21:54 +08:00
吴小白
024beca690 Merge pull request #9200 from jumpserver/pr@v2.28@perf_support_openid_pkce
perf: OpenID支持PKCE方式对接
2022-12-13 16:12:17 +08:00
jiangweidong
5c0359e394 perf: OpenID支持PKCE方式对接 2022-12-13 15:11:21 +08:00
feng
4ce4bde368 fix: ticket xss inject 2022-12-12 17:03:29 +08:00
halo
809bad271a fix: 密钥指纹参数 2022-12-09 13:41:38 +08:00
Eric
d3bfc03849 fix: 替换解析公钥的方式 2022-12-08 16:57:22 +08:00
Bai
04c0121b37 fix: 降级 Djanog==3.2.15 2022-12-08 14:53:40 +08:00
jiangweidong
b97b50ab31 perf: 支持sentinel开启ssl(Sentinel和Redis公用一套证书,无额外增加配置项) 2022-12-08 12:54:58 +08:00
Eric
d8a8c8153b fix: TraditionalOpenSSL private ssh key 2022-12-08 11:03:52 +08:00
Eric
a68ad7be68 perf: support ed25519 SSH Key
fix: codacy ci
fix: password use bytes
2022-12-08 11:03:52 +08:00
Bai
4041f1aeec fix: 修改 random_string 方法,支持只生成随机数字 2022-12-01 20:13:47 +08:00
feng
59388655ea fix: es 默认存储500 2022-11-18 17:04:43 +08:00
Bai
ef7463c588 fix: flower db file 持久化存储flower信息 2022-11-18 15:36:21 +08:00
Bryan
7e7d6d94e6 fix: 修复 channels-redis 库升级导致 ws 查看任务日志失败的问题; 修改 REDIS_LAYERS_HOST 变量; 修改 Channel SSL 配置项; 2022-11-18 15:26:44 +08:00
52 changed files with 827 additions and 521 deletions

View File

@@ -21,7 +21,7 @@ jobs:
TAG=$(basename ${GITHUB_REF}) TAG=$(basename ${GITHUB_REF})
VERSION=${TAG/v/} VERSION=${TAG/v/}
wget https://raw.githubusercontent.com/jumpserver/installer/master/quick_start.sh wget https://raw.githubusercontent.com/jumpserver/installer/master/quick_start.sh
sed -i "s@Version=.*@Version=v${VERSION}@g" quick_start.sh sed -i "s@VERSION=dev@VERSION=v${VERSION}@g" quick_start.sh
echo "::set-output name=TAG::$TAG" echo "::set-output name=TAG::$TAG"
echo "::set-output name=VERSION::$VERSION" echo "::set-output name=VERSION::$VERSION"
- name: Create Release - name: Create Release

View File

@@ -1,4 +1,4 @@
FROM python:3.8-slim as stage-build FROM python:3.8-slim-bullseye as stage-build
ARG TARGETARCH ARG TARGETARCH
ARG VERSION ARG VERSION
@@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
ADD . . ADD . .
RUN cd utils && bash -ixeu build.sh RUN cd utils && bash -ixeu build.sh
FROM python:3.8-slim FROM python:3.8-slim-bullseye
ARG TARGETARCH ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com> MAINTAINER JumpServer Team <ibuler@qq.com>

View File

@@ -38,8 +38,8 @@ ARG TOOLS=" \
default-mysql-client \ default-mysql-client \
iputils-ping \ iputils-ping \
locales \ locales \
netcat \ procps \
redis-server \ redis-tools \
telnet \ telnet \
vim \ vim \
unzip \ unzip \

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
5da6a1b9b6e038d667ccac1f38eacc6423ad6d2b

View File

@@ -1,4 +1,4 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.drf.serializers import BulkModelSerializer from common.drf.serializers import BulkModelSerializer
from common.drf.serializers import MethodSerializer from common.drf.serializers import MethodSerializer

View File

@@ -1,18 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from urllib3.exceptions import MaxRetryError
from urllib.parse import urlencode from urllib.parse import urlencode
from kubernetes import client
from kubernetes.client import api_client from kubernetes.client import api_client
from kubernetes.client.api import core_v1_api from kubernetes.client.api import core_v1_api
from kubernetes import client
from kubernetes.client.exceptions import ApiException
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
from common.utils import get_logger
from common.tree import TreeNode
from assets.models import SystemUser from assets.models import SystemUser
from common.tree import TreeNode
from common.utils import get_logger
from .. import const from .. import const
logger = get_logger(__file__) logger = get_logger(__file__)
@@ -23,7 +19,8 @@ class KubernetesClient:
self.url = url self.url = url
self.token = token self.token = token
def get_api(self): @property
def api(self):
configuration = client.Configuration() configuration = client.Configuration()
configuration.host = self.url configuration.host = self.url
configuration.verify_ssl = False configuration.verify_ssl = False
@@ -32,63 +29,46 @@ class KubernetesClient:
api = core_v1_api.CoreV1Api(c) api = core_v1_api.CoreV1Api(c)
return api return api
def get_namespace_list(self): def get_namespaces(self):
api = self.get_api() namespaces = []
namespace_list = [] resp = self.api.list_namespace()
for ns in api.list_namespace().items: for ns in resp.items:
namespace_list.append(ns.metadata.name) namespaces.append(ns.metadata.name)
return namespace_list return namespaces
def get_services(self): def get_pods(self, namespace):
api = self.get_api() pods = []
ret = api.list_service_for_all_namespaces(watch=False) resp = self.api.list_namespaced_pod(namespace)
for i in ret.items: for pd in resp.items:
print("%s \t%s \t%s \t%s \t%s \n" % ( pods.append(pd.metadata.name)
i.kind, i.metadata.namespace, i.metadata.name, i.spec.cluster_ip, i.spec.ports)) return pods
def get_pod_info(self, namespace, pod): def get_containers(self, namespace, pod_name):
api = self.get_api() containers = []
resp = api.read_namespaced_pod(namespace=namespace, name=pod) resp = self.api.read_namespaced_pod(pod_name, namespace)
return resp for container in resp.spec.containers:
containers.append(container.name)
return containers
def get_pod_logs(self, namespace, pod): @classmethod
api = self.get_api() def run(cls, asset, secret, tp='namespace'):
log_content = api.read_namespaced_pod_log(pod, namespace, pretty=True, tail_lines=200) k8s_url = f'{asset.address}'
return log_content k8s = cls(k8s_url, secret)
func_name = f'get_{tp}s'
if hasattr(k8s, func_name):
return getattr(k8s, func_name)()
return []
def get_pods(self): @classmethod
api = self.get_api() def get_kubernetes_data(cls, app_id, system_user_id, tp, *args):
try:
ret = api.list_pod_for_all_namespaces(watch=False, _request_timeout=(3, 3))
except MaxRetryError:
logger.warning('Kubernetes connection timed out')
return
except ApiException as e:
if e.status == 401:
logger.warning('Kubernetes User not authenticated')
else:
logger.warning(e)
return
data = {}
for i in ret.items:
namespace = i.metadata.namespace
pod_info = {
'pod_name': i.metadata.name,
'containers': [j.name for j in i.spec.containers]
}
if namespace in data:
data[namespace].append(pod_info)
else:
data[namespace] = [pod_info, ]
return data
@staticmethod
def get_kubernetes_data(app_id, system_user_id):
from ..models import Application from ..models import Application
app = get_object_or_404(Application, id=app_id) app = get_object_or_404(Application, id=app_id)
system_user = get_object_or_404(SystemUser, id=system_user_id) system_user = get_object_or_404(SystemUser, id=system_user_id)
k8s = KubernetesClient(app.attrs['cluster'], system_user.token) k8s = cls(app.attrs['cluster'], system_user.token)
return k8s.get_pods() func_name = f'get_{tp}s'
if hasattr(k8s, func_name):
return getattr(k8s, func_name)(*args)
return []
class KubernetesTree: class KubernetesTree:
@@ -118,11 +98,10 @@ class KubernetesTree:
) )
return node return node
def as_namespace_pod_tree_node(self, name, meta, type, counts=0, is_container=False): def as_namespace_pod_tree_node(self, name, meta, type, is_container=False):
from ..models import ApplicationTreeNodeMixin from ..models import ApplicationTreeNodeMixin
i = ApplicationTreeNodeMixin.create_tree_id(self.tree_id, type, name) i = ApplicationTreeNodeMixin.create_tree_id(self.tree_id, type, name)
meta.update({type: name}) meta.update({type: name})
name = name if is_container else f'{name}({counts})'
node = self.create_tree_node( node = self.create_tree_node(
i, self.tree_id, name, type, meta, icon='cloud', is_container=is_container i, self.tree_id, name, type, meta, icon='cloud', is_container=is_container
) )
@@ -157,30 +136,30 @@ class KubernetesTree:
system_user_id = parent_info.get('system_user_id') system_user_id = parent_info.get('system_user_id')
tree_nodes = [] tree_nodes = []
data = KubernetesClient.get_kubernetes_data(app_id, system_user_id)
if not data:
return tree_nodes
if pod_name: if pod_name:
for container in next( tp = 'container'
filter( containers = KubernetesClient.get_kubernetes_data(
lambda x: x['pod_name'] == pod_name, data[namespace] app_id, system_user_id, tp, namespace, pod_name
) )
)['containers']: for container in containers:
container_node = self.as_namespace_pod_tree_node( container_node = self.as_namespace_pod_tree_node(
container, parent_info, 'container', is_container=True container, parent_info, tp, is_container=True
) )
tree_nodes.append(container_node) tree_nodes.append(container_node)
elif namespace: elif namespace:
for pod in data[namespace]: tp = 'pod'
pod_nodes = self.as_namespace_pod_tree_node( pods = KubernetesClient.get_kubernetes_data(app_id, system_user_id, tp, namespace)
pod['pod_name'], parent_info, 'pod', len(pod['containers']) for pod in pods:
pod_node = self.as_namespace_pod_tree_node(
pod, parent_info, tp
) )
tree_nodes.append(pod_nodes) tree_nodes.append(pod_node)
elif system_user_id: elif system_user_id:
for namespace, pods in data.items(): tp = 'namespace'
namespaces = KubernetesClient.get_kubernetes_data(app_id, system_user_id, tp)
for namespace in namespaces:
namespace_node = self.as_namespace_pod_tree_node( namespace_node = self.as_namespace_pod_tree_node(
namespace, parent_info, 'namespace', len(pods) namespace, parent_info, tp
) )
tree_nodes.append(namespace_node) tree_nodes.append(namespace_node)
return tree_nodes return tree_nodes

View File

@@ -2,9 +2,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid
import logging import logging
from functools import reduce import uuid
from collections import OrderedDict from collections import OrderedDict
from django.db import models from django.db import models
@@ -14,7 +13,6 @@ from rest_framework.exceptions import ValidationError
from common.db.fields import JsonDictTextField from common.db.fields import JsonDictTextField
from common.utils import lazyproperty from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager from orgs.mixins.models import OrgModelMixin, OrgManager
from .base import AbsConnectivity from .base import AbsConnectivity
__all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet'] __all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet']

View File

@@ -1,28 +1,27 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import io
import os import os
import uuid import uuid
from hashlib import md5 from hashlib import md5
import sshpubkeys import sshpubkeys
from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models
from django.db.models import QuerySet
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.db.models import QuerySet
from common.utils import random_string, signer from common.db import fields
from common.utils import random_string
from common.utils import ( from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty
) )
from common.utils.encode import ssh_pubkey_gen from common.utils.encode import (
from common.validators import alphanumeric parse_ssh_public_key_str, parse_ssh_private_key_str
from common.db import fields )
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
logger = get_logger(__file__) logger = get_logger(__file__)
@@ -64,16 +63,16 @@ class AuthMixin:
@property @property
def ssh_key_fingerprint(self): def ssh_key_fingerprint(self):
public_key = None
if self.public_key: if self.public_key:
public_key = self.public_key public_key = self.public_key
elif self.private_key: elif self.private_key:
try: try:
public_key = ssh_pubkey_gen(private_key=self.private_key, password=self.password) public_key = parse_ssh_public_key_str(self.private_key, password=self.password)
except IOError as e: except IOError as e:
return str(e) return str(e)
else: if not public_key:
return '' return ''
public_key_obj = sshpubkeys.SSHKey(public_key) public_key_obj = sshpubkeys.SSHKey(public_key)
fingerprint = public_key_obj.hash_md5() fingerprint = public_key_obj.hash_md5()
return fingerprint return fingerprint
@@ -88,24 +87,29 @@ class AuthMixin:
@property @property
def private_key_file(self): def private_key_file(self):
if not self.private_key_obj: if not self.private_key:
return None
private_key_str = self.get_private_key()
if not private_key_str:
return None return None
project_dir = settings.PROJECT_DIR project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp') tmp_dir = os.path.join(project_dir, 'tmp')
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest() key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name) key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path): if not os.path.exists(key_path):
self.private_key_obj.write_private_key_file(key_path) with open(key_path, 'w') as f:
f.write(private_key_str)
os.chmod(key_path, 0o400) os.chmod(key_path, 0o400)
return key_path return key_path
def get_private_key(self): def get_private_key(self):
if not self.private_key_obj: if not self.private_key:
return None return None
string_io = io.StringIO() private_key_str = parse_ssh_private_key_str(self.private_key, password=self.password)
self.private_key_obj.write_private_key(string_io) if not private_key_str and self.password:
private_key = string_io.getvalue() # 由于历史原因,密码可能是真实的密码,而非私钥的 passphrase所以这里再尝试一次
return private_key private_key_str = parse_ssh_private_key_str(self.private_key)
return private_key_str
@property @property
def public_key_obj(self): def public_key_obj(self):
@@ -234,4 +238,3 @@ class BaseUser(OrgModelMixin, AuthMixin):
class Meta: class Meta:
abstract = True abstract = True

View File

@@ -4,16 +4,16 @@
import logging import logging
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.cache import cache from django.core.cache import cache
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from common.utils import signer, get_object_or_none from common.utils import signer, get_object_or_none, is_uuid
from .base import BaseUser
from .asset import Asset from .asset import Asset
from .authbook import AuthBook from .authbook import AuthBook
from .base import BaseUser
__all__ = ['AdminUser', 'SystemUser'] __all__ = ['AdminUser', 'SystemUser']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -187,22 +187,22 @@ class AuthMixin:
if username == '': if username == '':
username = self.username username = self.username
authbook = AuthBook.objects.filter( not_stu_query = Q(asset=asset, username=username, systemuser__isnull=True)
asset=asset, username=username, systemuser__isnull=True stu_query = Q(asset=asset, systemuser=self)
).order_by('-date_created').first() not_stu_qs = AuthBook.objects.filter(not_stu_query).order_by('-date_created')
stu_qs = AuthBook.objects.filter(stu_query).order_by('-date_created')
authbook = not_stu_qs.first()
if not authbook: if not authbook:
authbook = AuthBook.objects.filter( authbook = stu_qs.first()
asset=asset, systemuser=self
).order_by('-date_created').first()
if not authbook: if not authbook:
return None return None
authbook.load_auth() authbook.load_auth()
self.password = authbook.password
self.private_key = authbook.private_key self.password = authbook.password or self.password or ''
self.public_key = authbook.public_key self.private_key = authbook.private_key or self.private_key or ''
self.public_key = authbook.public_key or self.public_key or ''
def load_asset_more_auth(self, asset_id=None, username=None, user_id=None): def load_asset_more_auth(self, asset_id=None, username=None, user_id=None):
from users.models import User from users.models import User
@@ -249,12 +249,19 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser):
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users")) users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups")) groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type'))
priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)]) priority = models.IntegerField(
protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) default=81, verbose_name=_("Priority"),
help_text=_("1-100, the lower the value will be match first"),
validators=[MinValueValidator(1), MaxValueValidator(100)]
)
protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh',
verbose_name=_('Protocol'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) login_mode = models.CharField(
choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')
)
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root")) sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
token = models.TextField(default='', verbose_name=_('Token')) token = models.TextField(default='', verbose_name=_('Token'))
home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True) home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True)
@@ -262,7 +269,9 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser):
ad_domain = models.CharField(default='', max_length=256) ad_domain = models.CharField(default='', max_length=256)
# linux su 命令 (switch user) # linux su 命令 (switch user)
su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) su_enabled = models.BooleanField(default=False, verbose_name=_('User switch'))
su_from = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from")) su_from = models.ForeignKey(
'self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from")
)
def __str__(self): def __str__(self):
username = self.username username = self.username
@@ -322,9 +331,20 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser):
assets = Asset.objects.filter(id__in=asset_ids) assets = Asset.objects.filter(id__in=asset_ids)
return assets return assets
def filter_contain_protocol_assets(self, assets_or_ids):
if not assets_or_ids:
return assets_or_ids
if is_uuid(assets_or_ids[0]):
assets = Asset.objects.filter(id__in=assets_or_ids)
else:
assets = assets_or_ids
assets = [asset for asset in assets if self.protocol in asset.protocols_as_dict]
return assets
def add_related_assets(self, assets_or_ids): def add_related_assets(self, assets_or_ids):
self.assets.add(*tuple(assets_or_ids)) assets = self.filter_contain_protocol_assets(assets_or_ids)
self.add_related_assets_to_su_from_if_need(assets_or_ids) self.assets.add(*tuple(assets))
self.add_related_assets_to_su_from_if_need(assets)
def add_related_assets_to_su_from_if_need(self, assets_or_ids): def add_related_assets_to_su_from_if_need(self, assets_or_ids):
if self.protocol not in [self.Protocol.ssh.value]: if self.protocol not in [self.Protocol.ssh.value]:

View File

@@ -1,24 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from io import StringIO
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key
from common.drf.fields import EncryptedField
from assets.models import Type from assets.models import Type
from common.drf.fields import EncryptedField
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str, parse_ssh_public_key_str
from .utils import validate_password_for_ansible from .utils import validate_password_for_ansible
class AuthSerializer(serializers.ModelSerializer): class AuthSerializer(serializers.ModelSerializer):
password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024, label=_('Password')) password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024, label=_('Password'))
private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=16384, label=_('Private key')) private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=16384,
label=_('Private key'))
def gen_keys(self, private_key=None, password=None): def gen_keys(self, private_key=None, password=None):
if private_key is None: if private_key is None:
return None, None return None, None
public_key = ssh_pubkey_gen(private_key=private_key, password=password) public_key = parse_ssh_public_key_str(text=private_key, password=password)
return private_key, public_key return private_key, public_key
def save(self, **kwargs): def save(self, **kwargs):
@@ -57,10 +57,7 @@ class AuthSerializerMixin(serializers.ModelSerializer):
if not valid: if not valid:
raise serializers.ValidationError(_("private key invalid or passphrase error")) raise serializers.ValidationError(_("private key invalid or passphrase error"))
private_key = ssh_private_key_gen(private_key, password=passphrase) private_key = parse_ssh_private_key_str(private_key, password=passphrase)
string_io = StringIO()
private_key.write_private_key(string_io)
private_key = string_io.getvalue()
return private_key return private_key
def validate_public_key(self, public_key): def validate_public_key(self, public_key):

View File

@@ -1,16 +1,16 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count from django.db.models import Count
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.mixins.serializers import BulkSerializerMixin
from common.utils import ssh_pubkey_gen
from common.drf.fields import EncryptedField from common.drf.fields import EncryptedField
from common.drf.serializers import SecretReadableMixin from common.drf.serializers import SecretReadableMixin
from common.mixins.serializers import BulkSerializerMixin
from common.utils import parse_ssh_public_key_str
from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser, Asset
from .utils import validate_password_for_ansible
from .base import AuthSerializerMixin from .base import AuthSerializerMixin
from .utils import validate_password_for_ansible
from ..models import SystemUser, Asset
__all__ = [ __all__ = [
'SystemUserSerializer', 'MiniSystemUserSerializer', 'SystemUserSerializer', 'MiniSystemUserSerializer',
@@ -36,9 +36,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
token = EncryptedField( token = EncryptedField(
label=_('Token'), required=False, write_only=True, style={'base_template': 'textarea.html'} label=_('Token'), required=False, write_only=True, style={'base_template': 'textarea.html'}
) )
applications_amount = serializers.IntegerField(
source='apps_amount', read_only=True, label=_('Apps amount')
)
class Meta: class Meta:
model = SystemUser model = SystemUser
@@ -53,7 +50,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'su_enabled', 'su_from', 'su_enabled', 'su_from',
'date_created', 'date_updated', 'comment', 'created_by', 'date_created', 'date_updated', 'comment', 'created_by',
] ]
fields_m2m = ['cmd_filters', 'assets_amount', 'applications_amount', 'nodes'] fields_m2m = ['cmd_filters', 'nodes']
fields = fields_small + fields_m2m fields = fields_small + fields_m2m
extra_kwargs = { extra_kwargs = {
'cmd_filters': {"required": False, 'label': _('Command filter')}, 'cmd_filters': {"required": False, 'label': _('Command filter')},
@@ -214,7 +211,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
elif attrs.get('private_key'): elif attrs.get('private_key'):
private_key = attrs['private_key'] private_key = attrs['private_key']
password = attrs.get('password') password = attrs.get('password')
public_key = ssh_pubkey_gen(private_key, password=password, username=username) public_key = parse_ssh_public_key_str(private_key, password=password)
attrs['public_key'] = public_key attrs['public_key'] = public_key
return attrs return attrs
@@ -241,7 +238,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """
queryset = queryset \ queryset = queryset \
.annotate(assets_amount=Count("assets")) \
.prefetch_related('nodes', 'cmd_filters') .prefetch_related('nodes', 'cmd_filters')
return queryset return queryset

View File

@@ -1,6 +1,8 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
def validate_password_for_ansible(password): def validate_password_for_ansible(password):
""" 校验 Ansible 不支持的特殊字符 """ """ 校验 Ansible 不支持的特殊字符 """
@@ -15,3 +17,9 @@ def validate_password_for_ansible(password):
if '"' in password: if '"' in password:
raise serializers.ValidationError(_('Password can not contains `"` ')) raise serializers.ValidationError(_('Password can not contains `"` '))
def validate_ssh_key(ssh_key, passphrase=None):
valid = validate_ssh_private_key(ssh_key, password=passphrase)
if not valid:
raise serializers.ValidationError(_("private key invalid or passphrase error"))
return parse_ssh_private_key_str(ssh_key, passphrase)

View File

@@ -50,6 +50,10 @@ def clean_ansible_task_hosts(assets, system_user=None):
for asset in assets: for asset in assets:
if not check_asset_can_run_ansible(asset): if not check_asset_can_run_ansible(asset):
continue continue
# 资产平台不包含系统用户的协议, 不推送
if system_user and system_user.protocol not in asset.protocols_as_dict:
logger.info(_('Asset protocol not support system user protocol, skipped: {}').format(system_user.protocol))
continue
cleaned_assets.append(asset) cleaned_assets.append(asset)
if not cleaned_assets: if not cleaned_assets:
logger.info(_("No assets matched, stop task")) logger.info(_("No assets matched, stop task"))

View File

@@ -2,13 +2,14 @@ import csv
import codecs import codecs
from itertools import chain from itertools import chain
from datetime import datetime
from django.http import HttpResponse from django.http import HttpResponse
from django.db import models from django.db import models
from settings.serializers import SettingsSerializer from settings.serializers import SettingsSerializer
from common.utils.timezone import as_current_tz
from common.utils import validate_ip, get_ip_city, get_logger from common.utils import validate_ip, get_ip_city, get_logger
from common.db import fields
from .const import DEFAULT_CITY from .const import DEFAULT_CITY
@@ -78,6 +79,8 @@ def model_to_dict_for_operate_log(
f.verbose_name = 'id' f.verbose_name = 'id'
elif isinstance(value, list): elif isinstance(value, list):
value = [str(v) for v in value] value = [str(v) for v in value]
elif isinstance(value, datetime):
value = as_current_tz(value).strftime('%Y-%m-%d %H:%M:%S')
if include_model_fields or getattr(f, 'primary_key', False): if include_model_fields or getattr(f, 'primary_key', False):
data[str(f.verbose_name)] = value data[str(f.verbose_name)] = value

View File

@@ -1,26 +1,27 @@
import abc import abc
import os
import json
import base64 import base64
import json
import os
import urllib.parse import urllib.parse
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.exceptions import PermissionDenied
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response
from common.drf.api import JMSModelViewSet from common.drf.api import JMSModelViewSet
from common.http import is_true from common.http import is_true
from orgs.mixins.api import RootOrgViewMixin from orgs.mixins.api import RootOrgViewMixin
from perms.models.base import Action from perms.models.base import Action
from terminal.models import EndpointRule from terminal.models import EndpointRule
from ..models import ConnectionToken
from ..serializers import ( from ..serializers import (
ConnectionTokenSerializer, ConnectionTokenSecretSerializer, ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
SuperConnectionTokenSerializer, ConnectionTokenDisplaySerializer, SuperConnectionTokenSerializer, ConnectionTokenDisplaySerializer,
) )
from ..models import ConnectionToken
__all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet'] __all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet']
@@ -165,6 +166,9 @@ class ConnectionTokenMixin:
rdp_options['session bpp:i'] = os.getenv('JUMPSERVER_COLOR_DEPTH', '32') 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') rdp_options['audiomode:i'] = self.parse_env_bool('JUMPSERVER_DISABLE_AUDIO', 'false', '2', '0')
if token.asset and token.asset.platform.meta.get('console', None) == 'true':
rdp_options['administrative session:i:'] = '1'
if token.asset: if token.asset:
name = token.asset.hostname name = token.asset.hostname
elif token.application and token.application.category_remote_app: elif token.application and token.application.category_remote_app:

View File

@@ -20,6 +20,7 @@ class TicketStatusApi(mixins.AuthMixin, APIView):
try: try:
self.check_user_login_confirm() self.check_user_login_confirm()
self.request.session['auth_third_party_done'] = 1 self.request.session['auth_third_party_done'] = 1
self.request.session.pop('auth_third_party_required', '')
return Response({"msg": "ok"}) return Response({"msg": "ok"})
except errors.LoginConfirmOtherError as e: except errors.LoginConfirmOtherError as e:
reason = e.msg reason = e.msg

View File

@@ -2,6 +2,7 @@
# #
import requests import requests
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.utils.http import urlencode from django.utils.http import urlencode
from django.conf import settings from django.conf import settings
@@ -90,8 +91,12 @@ class OAuth2Backend(JMSModelBackend):
request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME) request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME)
) )
} }
access_token_url = '{url}?{query}'.format( if '?' in settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT:
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, query=urlencode(query_dict) separator = '&'
else:
separator = '?'
access_token_url = '{url}{separator}{query}'.format(
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, separator=separator, query=urlencode(query_dict)
) )
token_method = settings.AUTH_OAUTH2_ACCESS_TOKEN_METHOD.lower() token_method = settings.AUTH_OAUTH2_ACCESS_TOKEN_METHOD.lower()
requests_func = getattr(requests, token_method, requests.get) requests_func = getattr(requests, token_method, requests.get)
@@ -114,12 +119,16 @@ class OAuth2Backend(JMSModelBackend):
headers = { headers = {
'Accept': 'application/json', 'Accept': 'application/json',
'Authorization': 'token {}'.format(response_data.get('access_token', '')) 'Authorization': 'Bearer {}'.format(response_data.get('access_token', ''))
} }
logger.debug(log_prompt.format('Get userinfo endpoint')) logger.debug(log_prompt.format('Get userinfo endpoint'))
userinfo_url = '{url}?{query}'.format( if '?' in settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT:
url=settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT, separator = '&'
else:
separator = '?'
userinfo_url = '{url}{separator}{query}'.format(
url=settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT, separator=separator,
query=urlencode(query_dict) query=urlencode(query_dict)
) )
userinfo_response = requests.get(userinfo_url, headers=headers) userinfo_response = requests.get(userinfo_url, headers=headers)

View File

@@ -26,8 +26,13 @@ class OAuth2AuthRequestView(View):
) )
} }
redirect_url = '{url}?{query}'.format( if '?' in settings.AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT:
separator = '&'
else:
separator = '?'
redirect_url = '{url}{separator}{query}'.format(
url=settings.AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT, url=settings.AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT,
separator=separator,
query=urlencode(query_dict) query=urlencode(query_dict)
) )
logger.debug(log_prompt.format('Redirect login url')) logger.debug(log_prompt.format('Redirect login url'))

View File

@@ -88,7 +88,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
""" """
@ssl_verification @ssl_verification
def authenticate(self, request, nonce=None, **kwargs): def authenticate(self, request, nonce=None, code_verifier=None, **kwargs):
""" Authenticates users in case of the OpenID Connect Authorization code flow. """ """ Authenticates users in case of the OpenID Connect Authorization code flow. """
log_prompt = "Process authenticate [OIDCAuthCodeBackend]: {}" log_prompt = "Process authenticate [OIDCAuthCodeBackend]: {}"
logger.debug(log_prompt.format('start')) logger.debug(log_prompt.format('start'))
@@ -134,6 +134,8 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
request, path=reverse(settings.AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME) request, path=reverse(settings.AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME)
) )
} }
if settings.AUTH_OPENID_PKCE and code_verifier:
token_payload['code_verifier'] = code_verifier
if settings.AUTH_OPENID_CLIENT_AUTH_METHOD == 'client_secret_post': if settings.AUTH_OPENID_CLIENT_AUTH_METHOD == 'client_secret_post':
token_payload.update({ token_payload.update({
'client_id': settings.AUTH_OPENID_CLIENT_ID, 'client_id': settings.AUTH_OPENID_CLIENT_ID,

View File

@@ -9,7 +9,10 @@
""" """
import base64
import hashlib
import time import time
import secrets
from django.conf import settings from django.conf import settings
from django.contrib import auth from django.contrib import auth
@@ -38,6 +41,19 @@ class OIDCAuthRequestView(View):
http_method_names = ['get', ] http_method_names = ['get', ]
@staticmethod
def gen_code_verifier(length=128):
# length range 43 ~ 128
return secrets.token_urlsafe(length-32)
@staticmethod
def gen_code_challenge(code_verifier, code_challenge_method):
if code_challenge_method == 'plain':
return code_verifier
h = hashlib.sha256(code_verifier.encode('ascii')).digest()
b = base64.urlsafe_b64encode(h)
return b.decode('ascii')[:-1]
def get(self, request): def get(self, request):
""" Processes GET requests. """ """ Processes GET requests. """
@@ -56,6 +72,16 @@ class OIDCAuthRequestView(View):
) )
}) })
if settings.AUTH_OPENID_PKCE:
code_verifier = self.gen_code_verifier()
code_challenge_method = settings.AUTH_OPENID_CODE_CHALLENGE_METHOD or 'S256'
code_challenge = self.gen_code_challenge(code_verifier, code_challenge_method)
authentication_request_params.update({
'code_challenge_method': code_challenge_method,
'code_challenge': code_challenge
})
request.session['oidc_auth_code_verifier'] = code_verifier
# States should be used! They are recommended in order to maintain state between the # States should be used! They are recommended in order to maintain state between the
# authentication request and the callback. # authentication request and the callback.
if settings.AUTH_OPENID_USE_STATE: if settings.AUTH_OPENID_USE_STATE:
@@ -138,8 +164,9 @@ class OIDCAuthCallbackView(View):
# Authenticates the end-user. # Authenticates the end-user.
next_url = request.session.get('oidc_auth_next_url', None) next_url = request.session.get('oidc_auth_next_url', None)
code_verifier = request.session.get('oidc_auth_code_verifier', None)
logger.debug(log_prompt.format('Process authenticate')) logger.debug(log_prompt.format('Process authenticate'))
user = auth.authenticate(nonce=nonce, request=request) user = auth.authenticate(nonce=nonce, request=request, code_verifier=code_verifier)
if user and user.is_valid: if user and user.is_valid:
logger.debug(log_prompt.format('Login: {}'.format(user))) logger.debug(log_prompt.format('Login: {}'.format(user)))
auth.login(self.request, user) auth.login(self.request, user)

View File

@@ -61,6 +61,18 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
return response return response
if not request.session.get('auth_third_party_required'): if not request.session.get('auth_third_party_required'):
return response return response
white_urls = [
'jsi18n/', '/static/',
'login/guard', 'login/wait-confirm',
'login-confirm-ticket/status',
'settings/public/open',
'core/auth/login', 'core/auth/logout'
]
for url in white_urls:
if request.path.find(url) > -1:
return response
ip = get_request_ip(request) ip = get_request_ip(request)
try: try:
self.request = request self.request = request
@@ -88,7 +100,6 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
guard_url = "%s?%s" % (guard_url, args) guard_url = "%s?%s" % (guard_url, args)
response = redirect(guard_url) response = redirect(guard_url)
finally: finally:
request.session.pop('auth_third_party_required', '')
return response return response

View File

@@ -212,7 +212,8 @@ class MFAMixin:
self._do_check_user_mfa(code, mfa_type, user=user) self._do_check_user_mfa(code, mfa_type, user=user)
def check_user_mfa_if_need(self, user): def check_user_mfa_if_need(self, user):
if self.request.session.get('auth_mfa'): if self.request.session.get('auth_mfa') and \
self.request.session.get('auth_mfa_username') == user.username:
return return
if not user.mfa_enabled: if not user.mfa_enabled:
return return
@@ -220,14 +221,16 @@ class MFAMixin:
active_mfa_names = user.active_mfa_backends_mapper.keys() active_mfa_names = user.active_mfa_backends_mapper.keys()
raise errors.MFARequiredError(mfa_types=tuple(active_mfa_names)) raise errors.MFARequiredError(mfa_types=tuple(active_mfa_names))
def mark_mfa_ok(self, mfa_type): def mark_mfa_ok(self, mfa_type, user):
self.request.session['auth_mfa'] = 1 self.request.session['auth_mfa'] = 1
self.request.session['auth_mfa_username'] = user.username
self.request.session['auth_mfa_time'] = time.time() self.request.session['auth_mfa_time'] = time.time()
self.request.session['auth_mfa_required'] = 0 self.request.session['auth_mfa_required'] = 0
self.request.session['auth_mfa_type'] = mfa_type self.request.session['auth_mfa_type'] = mfa_type
MFABlockUtils(user.username, self.get_request_ip()).clean_failed_count()
def clean_mfa_mark(self): def clean_mfa_mark(self):
keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type'] keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type', 'auth_mfa_username']
for k in keys: for k in keys:
self.request.session.pop(k, '') self.request.session.pop(k, '')
@@ -262,7 +265,7 @@ class MFAMixin:
ok, msg = mfa_backend.check_code(code) ok, msg = mfa_backend.check_code(code)
if ok: if ok:
self.mark_mfa_ok(mfa_type) self.mark_mfa_ok(mfa_type, user)
return return
raise errors.MFAFailedError( raise errors.MFAFailedError(
@@ -369,7 +372,7 @@ class AuthACLMixin:
def check_user_login_confirm(self): def check_user_login_confirm(self):
ticket = self.get_ticket() ticket = self.get_ticket()
if not ticket: if not ticket:
raise errors.LoginConfirmOtherError('', "Not found") raise errors.LoginConfirmOtherError('', "Not found", '')
elif ticket.is_state(ticket.State.approved): elif ticket.is_state(ticket.State.approved):
self.request.session["auth_confirm_required"] = '' self.request.session["auth_confirm_required"] = ''
return return
@@ -512,4 +515,20 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost
args = self.request.META.get('QUERY_STRING', '') args = self.request.META.get('QUERY_STRING', '')
if args: if args:
guard_url = "%s?%s" % (guard_url, args) guard_url = "%s?%s" % (guard_url, args)
return redirect(guard_url) response = redirect(guard_url)
self.set_browser_default_language_if_need(response)
return response
def set_browser_default_language_if_need(self, response):
# en, ja, zh-CN,zh;q=0.9
browser_lang = self.request.headers.get('Accept-Language', '')
# 浏览器首选语言
if browser_lang.startswith('en'):
browser_lang = 'en'
elif browser_lang.startswith('ja'):
browser_lang = 'ja'
else:
browser_lang = 'zh'
request_lang = self.request.LANGUAGE_CODE
lang = request_lang or browser_lang
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)

View File

@@ -60,7 +60,7 @@ class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
'state': state, 'state': state,
'redirect_uri': redirect_uri, 'redirect_uri': redirect_uri,
} }
url = URL.AUTHEN + '?' + urlencode(params) url = URL().authen + '?' + urlencode(params)
return url return url
@staticmethod @staticmethod

View File

@@ -32,11 +32,14 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
return super().get(*args, **kwargs) return super().get(*args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
from users.utils import MFABlockUtils
code = form.cleaned_data.get('code') code = form.cleaned_data.get('code')
mfa_type = form.cleaned_data.get('mfa_type') mfa_type = form.cleaned_data.get('mfa_type')
try: try:
self._do_check_user_mfa(code, mfa_type) self._do_check_user_mfa(code, mfa_type)
user, ip = self.get_user_from_session(), self.get_request_ip()
MFABlockUtils(user.username, ip).clean_failed_count()
return redirect_to_guard_view('mfa_ok') return redirect_to_guard_view('mfa_ok')
except (errors.MFAFailedError, errors.BlockMFAError) as e: except (errors.MFAFailedError, errors.BlockMFAError) as e:
form.add_error('code', e.msg) form.add_error('code', e.msg)

View File

@@ -1,7 +1,7 @@
from werkzeug.local import Local from werkzeug.local import Local
thread_local = Local() thread_local = Local()
encrypted_field_set = set() encrypted_field_set = {'password'}
def _find(attr): def _find(attr):

View File

@@ -9,6 +9,10 @@ class FlowerService(BaseService):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@property
def db_file(self):
return os.path.join(BASE_DIR, 'data', 'flower')
@property @property
def cmd(self): def cmd(self):
print("\n- Start Flower as Task Monitor") print("\n- Start Flower as Task Monitor")
@@ -20,11 +24,11 @@ class FlowerService(BaseService):
'-A', 'ops', '-A', 'ops',
'flower', 'flower',
'-logging=info', '-logging=info',
'-db={}'.format(self.db_file),
'--url_prefix=/core/flower', '--url_prefix=/core/flower',
'--auto_refresh=False', '--auto_refresh=False',
'--max_tasks=1000', '--max_tasks=1000',
'--persistent=True', '--persistent=True',
'-db=/opt/jumpserver/data/flower.db',
'--state_save_interval=600000' '--state_save_interval=600000'
] ]
return cmd return cmd

View File

@@ -3,6 +3,7 @@ import json
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
from django.conf import settings
from common.utils.common import get_logger from common.utils.common import get_logger
from common.sdk.im.utils import digest from common.sdk.im.utils import digest
from common.sdk.im.mixin import RequestMixin, BaseRequest from common.sdk.im.mixin import RequestMixin, BaseRequest
@@ -11,14 +12,30 @@ logger = get_logger(__name__)
class URL: class URL:
AUTHEN = 'https://open.feishu.cn/open-apis/authen/v1/index'
GET_TOKEN = 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/'
# https://open.feishu.cn/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN # https://open.feishu.cn/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN
GET_USER_INFO_BY_CODE = 'https://open.feishu.cn/open-apis/authen/v1/access_token' @property
def host(self):
if settings.FEISHU_VERSION == 'feishu':
h = 'https://open.feishu.cn'
else:
h = 'https://open.larksuite.com'
return h
SEND_MESSAGE = 'https://open.feishu.cn/open-apis/im/v1/messages' @property
def authen(self):
return f'{self.host}/open-apis/authen/v1/index'
@property
def get_token(self):
return f'{self.host}/open-apis/auth/v3/tenant_access_token/internal/'
@property
def get_user_info_by_code(self):
return f'{self.host}/open-apis/authen/v1/access_token'
@property
def send_message(self):
return f'{self.host}/open-apis/im/v1/messages'
class ErrorCode: class ErrorCode:
@@ -51,7 +68,7 @@ class FeishuRequests(BaseRequest):
def request_access_token(self): def request_access_token(self):
data = {'app_id': self._app_id, 'app_secret': self._app_secret} data = {'app_id': self._app_id, 'app_secret': self._app_secret}
response = self.raw_request('post', url=URL.GET_TOKEN, data=data) response = self.raw_request('post', url=URL().get_token, data=data)
self.check_errcode_is_0(response) self.check_errcode_is_0(response)
access_token = response['tenant_access_token'] access_token = response['tenant_access_token']
@@ -86,7 +103,7 @@ class FeiShu(RequestMixin):
'code': code 'code': code
} }
data = self._requests.post(URL.GET_USER_INFO_BY_CODE, json=body, check_errcode_is_0=False) data = self._requests.post(URL().get_user_info_by_code, json=body, check_errcode_is_0=False)
self._requests.check_errcode_is_0(data) self._requests.check_errcode_is_0(data)
return data['data']['user_id'] return data['data']['user_id']
@@ -107,7 +124,7 @@ class FeiShu(RequestMixin):
try: try:
logger.info(f'Feishu send text: user_ids={user_ids} msg={msg}') logger.info(f'Feishu send text: user_ids={user_ids} msg={msg}')
self._requests.post(URL.SEND_MESSAGE, params=params, json=body) self._requests.post(URL().send_message, params=params, json=body)
except APIException as e: except APIException as e:
# 只处理可预知的错误 # 只处理可预知的错误
logger.exception(e) logger.exception(e)

View File

@@ -1,24 +1,23 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import re
import json
from six import string_types
import base64 import base64
import os
import time
import hashlib import hashlib
import json
import os
import re
import time
from io import StringIO from io import StringIO
from itertools import chain
import paramiko import paramiko
import sshpubkeys import sshpubkeys
from cryptography.hazmat.primitives import serialization
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from itsdangerous import ( from itsdangerous import (
TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer,
BadSignature, SignatureExpired BadSignature, SignatureExpired
) )
from django.conf import settings from six import string_types
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.fields.files import FileField
from .http import http_date from .http import http_date
@@ -69,22 +68,25 @@ class Signer(metaclass=Singleton):
return None return None
_supported_paramiko_ssh_key_types = (
paramiko.RSAKey,
paramiko.DSSKey,
paramiko.Ed25519Key,
paramiko.ECDSAKey,
)
def ssh_key_string_to_obj(text, password=None): def ssh_key_string_to_obj(text, password=None):
key = None key = None
try: for ssh_key_type in _supported_paramiko_ssh_key_types:
key = paramiko.RSAKey.from_private_key(StringIO(text), password=password) if not issubclass(ssh_key_type, paramiko.PKey):
except paramiko.SSHException: continue
pass try:
else: key = ssh_key_type.from_private_key(StringIO(text), password=password)
return key except paramiko.SSHException:
pass
try: else:
key = paramiko.DSSKey.from_private_key(StringIO(text), password=password) return key
except paramiko.SSHException:
pass
else:
return key
return key return key
@@ -98,7 +100,7 @@ def ssh_private_key_gen(private_key, password=None):
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None): def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
private_key = ssh_private_key_gen(private_key, password=password) private_key = ssh_private_key_gen(private_key, password=password)
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)): if not isinstance(private_key, _supported_paramiko_ssh_key_types):
raise IOError('Invalid private key') raise IOError('Invalid private key')
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % { public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
@@ -137,17 +139,65 @@ def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', h
def validate_ssh_private_key(text, password=None): def validate_ssh_private_key(text, password=None):
if isinstance(text, bytes): key = parse_ssh_private_key_str(text, password=password)
try: return bool(key)
text = text.decode("utf-8")
except UnicodeDecodeError:
return False
key = ssh_key_string_to_obj(text, password=password)
if key is None: def parse_ssh_private_key_str(text: bytes, password=None) -> str:
return False private_key = _parse_ssh_private_key(text, password=password)
else: if private_key is None:
return True return ""
private_key_bytes = private_key.private_bytes(serialization.Encoding.PEM,
serialization.PrivateFormat.OpenSSH,
serialization.NoEncryption())
return private_key_bytes.decode('utf-8')
def parse_ssh_public_key_str(text: bytes = "", password=None) -> str:
private_key = _parse_ssh_private_key(text, password=password)
if private_key is None:
return ""
public_key_bytes = private_key.public_key().public_bytes(
serialization.Encoding.OpenSSH,
serialization.PublicFormat.OpenSSH)
return public_key_bytes.decode('utf-8')
def _parse_ssh_private_key(text, password=None):
"""
text: bytes
password: str
return:private key types:
ec.EllipticCurvePrivateKey,
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ed25519.Ed25519PrivateKey,
"""
if not bool(password):
password = None
if isinstance(text, str):
try:
text = text.encode("utf-8")
except UnicodeDecodeError:
return None
if password is not None:
if isinstance(password, str):
try:
password = password.encode("utf-8")
except UnicodeDecodeError:
return None
try:
if is_openssh_format_key(text):
return serialization.load_ssh_private_key(text, password=password)
return serialization.load_pem_private_key(text, password=password)
except (ValueError, TypeError):
pass
return None
def is_openssh_format_key(text: bytes):
return text.startswith(b"-----BEGIN OPENSSH PRIVATE KEY-----")
def validate_ssh_public_key(text): def validate_ssh_public_key(text):
@@ -226,4 +276,4 @@ def ensure_last_char_is_ascii(data):
def data_to_json(data, sort_keys=True, indent=2, cls=None): def data_to_json(data, sort_keys=True, indent=2, cls=None):
if cls is None: if cls is None:
cls = DjangoJSONEncoder cls = DjangoJSONEncoder
return json.dumps(data, sort_keys=sort_keys, indent=indent, cls=cls) return json.dumps(data, ensure_ascii=False, sort_keys=sort_keys, indent=indent, cls=cls)

View File

@@ -18,25 +18,35 @@ def random_ip():
return socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff))) return socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff)))
def random_string(length, lower=True, upper=True, digit=True, special_char=False): def random_string(length: int, lower=True, upper=True, digit=True, special_char=False):
chars = string.ascii_letters args_names = ['lower', 'upper', 'digit', 'special_char']
if digit: args_values = [lower, upper, digit, special_char]
chars += string.digits args_string = [string.ascii_lowercase, string.ascii_uppercase, string.digits, string_punctuation]
args_string_map = dict(zip(args_names, args_string))
kwargs = dict(zip(args_names, args_values))
kwargs_keys = list(kwargs.keys())
kwargs_values = list(kwargs.values())
args_true_count = len([i for i in kwargs_values if i])
assert any(kwargs_values), f'Parameters {kwargs_keys} must have at least one `True`'
assert length >= args_true_count, f'Expected length >= {args_true_count}, bug got {length}'
can_startswith_special_char = args_true_count == 1 and special_char
chars = ''.join([args_string_map[k] for k, v in kwargs.items() if v])
while True: while True:
password = list(random.choice(chars) for i in range(length)) password = list(random.choice(chars) for i in range(length))
if upper and not any(c.upper() for c in password): for k, v in kwargs.items():
continue if v and not (set(password) & set(args_string_map[k])):
if lower and not any(c.lower() for c in password): # 没有包含指定的字符, retry
continue break
if digit and not any(c.isdigit() for c in password): else:
continue if not can_startswith_special_char and password[0] in args_string_map['special_char']:
break # 首位不能为特殊字符, retry
continue
if special_char: else:
spc = random.choice(string_punctuation) # 满足要求终止 while 循环
i = random.choice(range(1, len(password))) break
password[i] = spc
password = ''.join(password) password = ''.join(password)
return password return password

View File

@@ -202,6 +202,7 @@ class Config(dict):
'REDIS_SSL_KEY': None, 'REDIS_SSL_KEY': None,
'REDIS_SSL_CERT': None, 'REDIS_SSL_CERT': None,
'REDIS_SSL_CA': None, 'REDIS_SSL_CA': None,
'REDIS_SSL_REQUIRED': 'none',
# Redis Sentinel # Redis Sentinel
'REDIS_SENTINEL_HOSTS': '', 'REDIS_SENTINEL_HOSTS': '',
'REDIS_SENTINEL_PASSWORD': '', 'REDIS_SENTINEL_PASSWORD': '',
@@ -269,6 +270,8 @@ class Config(dict):
'AUTH_OPENID_USER_ATTR_MAP': { 'AUTH_OPENID_USER_ATTR_MAP': {
'name': 'name', 'username': 'preferred_username', 'email': 'email' 'name': 'name', 'username': 'preferred_username', 'email': 'email'
}, },
'AUTH_OPENID_PKCE': False,
'AUTH_OPENID_CODE_CHALLENGE_METHOD': 'S256',
# OpenID 新配置参数 (version >= 1.5.9) # OpenID 新配置参数 (version >= 1.5.9)
'AUTH_OPENID_PROVIDER_ENDPOINT': 'https://oidc.example.com/', 'AUTH_OPENID_PROVIDER_ENDPOINT': 'https://oidc.example.com/',
@@ -373,6 +376,7 @@ class Config(dict):
'AUTH_FEISHU': False, 'AUTH_FEISHU': False,
'FEISHU_APP_ID': '', 'FEISHU_APP_ID': '',
'FEISHU_APP_SECRET': '', 'FEISHU_APP_SECRET': '',
'FEISHU_VERSION': 'feishu',
'LOGIN_REDIRECT_TO_BACKEND': '', # 'OPENID / CAS / SAML2 'LOGIN_REDIRECT_TO_BACKEND': '', # 'OPENID / CAS / SAML2
'LOGIN_REDIRECT_MSG_ENABLED': True, 'LOGIN_REDIRECT_MSG_ENABLED': True,

View File

@@ -78,6 +78,8 @@ AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION
AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION
AUTH_OPENID_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER AUTH_OPENID_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER
AUTH_OPENID_USER_ATTR_MAP = CONFIG.AUTH_OPENID_USER_ATTR_MAP AUTH_OPENID_USER_ATTR_MAP = CONFIG.AUTH_OPENID_USER_ATTR_MAP
AUTH_OPENID_PKCE = CONFIG.AUTH_OPENID_PKCE
AUTH_OPENID_CODE_CHALLENGE_METHOD = CONFIG.AUTH_OPENID_CODE_CHALLENGE_METHOD
AUTH_OPENID_AUTH_LOGIN_URL_NAME = 'authentication:openid:login' AUTH_OPENID_AUTH_LOGIN_URL_NAME = 'authentication:openid:login'
AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:openid:login-callback' AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:openid:login-callback'
AUTH_OPENID_AUTH_LOGOUT_URL_NAME = 'authentication:openid:logout' AUTH_OPENID_AUTH_LOGOUT_URL_NAME = 'authentication:openid:logout'
@@ -135,6 +137,7 @@ DINGTALK_APPSECRET = CONFIG.DINGTALK_APPSECRET
AUTH_FEISHU = CONFIG.AUTH_FEISHU AUTH_FEISHU = CONFIG.AUTH_FEISHU
FEISHU_APP_ID = CONFIG.FEISHU_APP_ID FEISHU_APP_ID = CONFIG.FEISHU_APP_ID
FEISHU_APP_SECRET = CONFIG.FEISHU_APP_SECRET FEISHU_APP_SECRET = CONFIG.FEISHU_APP_SECRET
FEISHU_VERSION = CONFIG.FEISHU_VERSION
# Saml2 auth # Saml2 auth
AUTH_SAML2 = CONFIG.AUTH_SAML2 AUTH_SAML2 = CONFIG.AUTH_SAML2

View File

@@ -1,6 +1,9 @@
import os import os
import platform import platform
from redis.sentinel import SentinelManagedSSLConnection
if platform.system() == 'Darwin' and platform.machine() == 'arm64': if platform.system() == 'Darwin' and platform.machine() == 'arm64':
import pymysql import pymysql
@@ -195,7 +198,7 @@ DATABASES = {
} }
} }
DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'certs', 'db_ca.pem') DB_CA_PATH = os.path.join(CERTS_DIR, 'db_ca.pem')
DB_USE_SSL = False DB_USE_SSL = False
if CONFIG.DB_ENGINE.lower() == 'mysql': if CONFIG.DB_ENGINE.lower() == 'mysql':
DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'" DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'"
@@ -317,10 +320,19 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
'CLIENT_CLASS': 'django_redis.client.SentinelClient', 'CLIENT_CLASS': 'django_redis.client.SentinelClient',
'SENTINELS': REDIS_SENTINELS, 'PASSWORD': CONFIG.REDIS_PASSWORD, 'SENTINELS': REDIS_SENTINELS, 'PASSWORD': CONFIG.REDIS_PASSWORD,
'SENTINEL_KWARGS': { 'SENTINEL_KWARGS': {
'ssl': REDIS_USE_SSL,
'ssl_cert_reqs': REDIS_SSL_REQUIRED,
"ssl_keyfile": REDIS_SSL_KEY,
"ssl_certfile": REDIS_SSL_CERT,
"ssl_ca_certs": REDIS_SSL_CA,
'password': REDIS_SENTINEL_PASSWORD, 'password': REDIS_SENTINEL_PASSWORD,
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT 'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT
} }
}) })
if REDIS_USE_SSL:
REDIS_OPTIONS['CONNECTION_POOL_KWARGS'].update({
'connection_class': SentinelManagedSSLConnection
})
DJANGO_REDIS_CONNECTION_FACTORY = 'django_redis.pool.SentinelConnectionFactory' DJANGO_REDIS_CONNECTION_FACTORY = 'django_redis.pool.SentinelConnectionFactory'
else: else:
REDIS_LOCATION_NO_DB = '%(protocol)s://:%(password)s@%(host)s:%(port)s/{}' % { REDIS_LOCATION_NO_DB = '%(protocol)s://:%(password)s@%(host)s:%(port)s/{}' % {

View File

@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import os import os
import ssl from urllib.parse import urlencode
from .base import ( from .base import (
REDIS_SSL_CA, REDIS_SSL_CERT, REDIS_SSL_KEY, REDIS_SSL_REQUIRED, REDIS_USE_SSL, REDIS_SSL_CA, REDIS_SSL_CERT, REDIS_SSL_KEY, REDIS_SSL_REQUIRED, REDIS_USE_SSL,
REDIS_SENTINEL_SERVICE_NAME, REDIS_SENTINELS, REDIS_SENTINEL_PASSWORD, REDIS_PROTOCOL, REDIS_SENTINEL_SERVICE_NAME, REDIS_SENTINELS, REDIS_SENTINEL_PASSWORD,
REDIS_SENTINEL_SOCKET_TIMEOUT REDIS_SENTINEL_SOCKET_TIMEOUT
) )
from ..const import CONFIG, PROJECT_DIR from ..const import CONFIG, PROJECT_DIR
@@ -81,41 +81,54 @@ BOOTSTRAP3 = {
} }
# Django channels support websocket # Django channels support websocket
if not REDIS_USE_SSL: REDIS_LAYERS_HOST = {
redis_ssl = None
else:
redis_ssl = ssl.SSLContext()
redis_ssl.check_hostname = bool(CONFIG.REDIS_SSL_REQUIRED)
if REDIS_SSL_CA:
redis_ssl.load_verify_locations(REDIS_SSL_CA)
if REDIS_SSL_CERT and REDIS_SSL_KEY:
redis_ssl.load_cert_chain(REDIS_SSL_CERT, REDIS_SSL_KEY)
REDIS_HOST = {
'db': CONFIG.REDIS_DB_WS, 'db': CONFIG.REDIS_DB_WS,
'password': CONFIG.REDIS_PASSWORD or None, 'password': CONFIG.REDIS_PASSWORD or None,
'ssl': redis_ssl,
} }
REDIS_LAYERS_SSL_PARAMS = {}
if REDIS_USE_SSL:
REDIS_LAYERS_SSL_PARAMS.update({
'ssl': REDIS_USE_SSL,
'ssl_cert_reqs': REDIS_SSL_REQUIRED,
"ssl_keyfile": REDIS_SSL_KEY,
"ssl_certfile": REDIS_SSL_CERT,
"ssl_ca_certs": REDIS_SSL_CA
})
REDIS_LAYERS_HOST.update(REDIS_LAYERS_SSL_PARAMS)
if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS: if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
REDIS_HOST['sentinels'] = REDIS_SENTINELS REDIS_LAYERS_HOST['sentinels'] = REDIS_SENTINELS
REDIS_HOST['master_name'] = REDIS_SENTINEL_SERVICE_NAME REDIS_LAYERS_HOST['master_name'] = REDIS_SENTINEL_SERVICE_NAME
REDIS_HOST['sentinel_kwargs'] = { REDIS_LAYERS_HOST['sentinel_kwargs'] = {
'password': REDIS_SENTINEL_PASSWORD, 'password': REDIS_SENTINEL_PASSWORD,
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT 'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT,
'ssl': REDIS_USE_SSL,
'ssl_cert_reqs': REDIS_SSL_REQUIRED,
"ssl_keyfile": REDIS_SSL_KEY,
"ssl_certfile": REDIS_SSL_CERT,
"ssl_ca_certs": REDIS_SSL_CA
} }
else: else:
REDIS_HOST['address'] = (CONFIG.REDIS_HOST, CONFIG.REDIS_PORT) # More info see: https://github.com/django/channels_redis/issues/334
# REDIS_LAYERS_HOST['address'] = (CONFIG.REDIS_HOST, CONFIG.REDIS_PORT)
REDIS_LAYERS_ADDRESS = '{protocol}://:{password}@{host}:{port}/{db}'.format(
protocol=REDIS_PROTOCOL, password=CONFIG.REDIS_PASSWORD,
host=CONFIG.REDIS_HOST, port=CONFIG.REDIS_PORT, db=CONFIG.REDIS_DB_WS
)
REDIS_LAYERS_SSL_PARAMS.pop('ssl', None)
REDIS_LAYERS_HOST['address'] = '{}?{}'.format(REDIS_LAYERS_ADDRESS, urlencode(REDIS_LAYERS_SSL_PARAMS))
CHANNEL_LAYERS = { CHANNEL_LAYERS = {
'default': { 'default': {
'BACKEND': 'common.cache.RedisChannelLayer', 'BACKEND': 'common.cache.RedisChannelLayer',
'CONFIG': { 'CONFIG': {
"hosts": [REDIS_HOST], "hosts": [REDIS_LAYERS_HOST],
}, },
}, },
} }
ASGI_APPLICATION = 'jumpserver.routing.application' ASGI_APPLICATION = 'jumpserver.routing.application'
# Dump all celery log to here # Dump all celery log to here
@@ -132,13 +145,18 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
'master_name': REDIS_SENTINEL_SERVICE_NAME, 'master_name': REDIS_SENTINEL_SERVICE_NAME,
'sentinel_kwargs': { 'sentinel_kwargs': {
'password': REDIS_SENTINEL_PASSWORD, 'password': REDIS_SENTINEL_PASSWORD,
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT 'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT,
'ssl': REDIS_USE_SSL,
'ssl_cert_reqs': REDIS_SSL_REQUIRED,
"ssl_keyfile": REDIS_SSL_KEY,
"ssl_certfile": REDIS_SSL_CERT,
"ssl_ca_certs": REDIS_SSL_CA
} }
} }
CELERY_BROKER_TRANSPORT_OPTIONS = CELERY_RESULT_BACKEND_TRANSPORT_OPTIONS = SENTINEL_OPTIONS CELERY_BROKER_TRANSPORT_OPTIONS = CELERY_RESULT_BACKEND_TRANSPORT_OPTIONS = SENTINEL_OPTIONS
else: else:
CELERY_BROKER_URL = CELERY_BROKER_URL_FORMAT % { CELERY_BROKER_URL = CELERY_BROKER_URL_FORMAT % {
'protocol': 'rediss' if REDIS_USE_SSL else 'redis', 'protocol': REDIS_PROTOCOL,
'password': CONFIG.REDIS_PASSWORD, 'password': CONFIG.REDIS_PASSWORD,
'host': CONFIG.REDIS_HOST, 'host': CONFIG.REDIS_HOST,
'port': CONFIG.REDIS_PORT, 'port': CONFIG.REDIS_PORT,

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7db985efdf818137dafe489f339955f4d71a245ffd6becc8f6efac539a625682 oid sha256:0cc37a87259f2cd3794ad628ee40497bd97beaee7219be3d9773e1e544c3e352
size 133463 size 134193

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-11-17 17:34+0800\n" "POT-Creation-Date: 2023-05-08 18:10+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"
@@ -24,13 +24,13 @@ msgstr "Acls"
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
#: applications/models/application.py:219 assets/models/asset.py:138 #: applications/models/application.py:219 assets/models/asset.py:138
#: assets/models/base.py:175 assets/models/cluster.py:18 #: assets/models/base.py:177 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:27 assets/models/domain.py:23 #: assets/models/cmd_filter.py:27 assets/models/domain.py:23
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 #: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29
#: settings/models.py:33 settings/serializers/sms.py:6 #: settings/models.py:33 settings/serializers/sms.py:6
#: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87 #: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87
#: terminal/models/storage.py:27 terminal/models/task.py:16 #: terminal/models/storage.py:26 terminal/models/task.py:16
#: terminal/models/terminal.py:101 users/forms/profile.py:33 #: terminal/models/terminal.py:101 users/forms/profile.py:33
#: users/models/group.py:15 users/models/user.py:673 #: users/models/group.py:15 users/models/user.py:673
#: xpack/plugins/cloud/models.py:27 #: xpack/plugins/cloud/models.py:27
@@ -55,14 +55,14 @@ msgstr "アクティブ"
#: acls/models/base.py:32 applications/models/application.py:232 #: acls/models/base.py:32 applications/models/application.py:232
#: assets/models/asset.py:143 assets/models/asset.py:231 #: assets/models/asset.py:143 assets/models/asset.py:231
#: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/backup.py:54 assets/models/base.py:182
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:52 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:52
#: assets/models/cmd_filter.py:100 assets/models/domain.py:24 #: assets/models/cmd_filter.py:100 assets/models/domain.py:24
#: assets/models/domain.py:65 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:73 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38
#: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97 #: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97
#: terminal/models/storage.py:30 terminal/models/terminal.py:115 #: terminal/models/storage.py:29 terminal/models/terminal.py:115
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
#: users/models/group.py:16 users/models/user.py:712 #: users/models/group.py:16 users/models/user.py:712
#: xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/change_auth_plan/models/base.py:44
@@ -131,7 +131,7 @@ msgstr "システムユーザー"
#: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/asset.py:386 assets/models/authbook.py:19
#: assets/models/backup.py:31 assets/models/cmd_filter.py:42 #: assets/models/backup.py:31 assets/models/cmd_filter.py:42
#: 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:40 #: assets/serializers/system_user.py:264 audits/models.py:40
#: authentication/models.py:66 authentication/models.py:90 #: authentication/models.py:66 authentication/models.py:90
#: 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:47 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:47
@@ -155,7 +155,7 @@ msgid "Format for comma-delimited string, with * indicating a match all. "
msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。" msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。"
#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:178
#: assets/models/gathered_user.py:15 audits/models.py:139 #: assets/models/gathered_user.py:15 audits/models.py:139
#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260 #: authentication/models.py:260
@@ -186,7 +186,7 @@ msgstr ""
#: 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
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8
#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:54 #: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:58
msgid "IP" msgid "IP"
msgstr "IP" msgstr "IP"
@@ -269,7 +269,7 @@ 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:46 assets/models/user.py:343 audits/models.py:41 #: assets/models/cmd_filter.py:46 assets/models/user.py:354 audits/models.py:41
#: authentication/models.py:83 perms/models/application_permission.py:33 #: authentication/models.py:83 perms/models/application_permission.py:33
#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22
#: terminal/backends/command/serializers.py:36 terminal/models/session.py:49 #: terminal/backends/command/serializers.py:36 terminal/models/session.py:49
@@ -280,7 +280,7 @@ msgid "System user"
msgstr "システムユーザー" msgstr "システムユーザー"
#: applications/models/account.py:17 assets/models/authbook.py:21 #: applications/models/account.py:17 assets/models/authbook.py:21
#: settings/serializers/auth/cas.py:20 #: settings/serializers/auth/cas.py:20 settings/serializers/auth/feishu.py:20
msgid "Version" msgid "Version"
msgstr "バージョン" msgstr "バージョン"
@@ -310,7 +310,7 @@ msgstr "カテゴリ"
#: assets/models/cmd_filter.py:86 assets/models/user.py:251 #: assets/models/cmd_filter.py:86 assets/models/user.py:251
#: authentication/models.py:70 perms/models/application_permission.py:24 #: authentication/models.py:70 perms/models/application_permission.py:24
#: perms/serializers/application/user_permission.py:34 #: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:59 terminal/models/storage.py:145 #: terminal/models/storage.py:58 terminal/models/storage.py:147
#: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/comment.py:26 tickets/models/flow.py:57
#: tickets/models/ticket/apply_application.py:18 #: tickets/models/ticket/apply_application.py:18
#: tickets/models/ticket/general.py:273 #: tickets/models/ticket/general.py:273
@@ -353,7 +353,7 @@ msgid "Type display"
msgstr "タイプ表示" msgstr "タイプ表示"
#: applications/serializers/application.py:105 assets/models/asset.py:230 #: applications/serializers/application.py:105 assets/models/asset.py:230
#: assets/models/base.py:181 assets/models/cluster.py:26 #: assets/models/base.py:183 assets/models/cluster.py:26
#: assets/models/cmd_filter.py:53 assets/models/domain.py:26 #: assets/models/cmd_filter.py:53 assets/models/domain.py:26
#: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/account.py:18 #: assets/models/label.py:25 assets/serializers/account.py:18
@@ -367,7 +367,7 @@ msgstr "タイプ表示"
msgid "Date created" msgid "Date created"
msgstr "作成された日付" msgstr "作成された日付"
#: applications/serializers/application.py:106 assets/models/base.py:182 #: applications/serializers/application.py:106 assets/models/base.py:184
#: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20 #: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20
#: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29 #: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29
#: assets/serializers/cmd_filter.py:49 common/db/models.py:117 #: assets/serializers/cmd_filter.py:49 common/db/models.py:117
@@ -445,7 +445,7 @@ msgid "Application path"
msgstr "アプリケーションパス" msgstr "アプリケーションパス"
#: applications/serializers/attrs/application_category/remote_app.py:44 #: applications/serializers/attrs/application_category/remote_app.py:44
#: assets/serializers/system_user.py:167 #: assets/serializers/system_user.py:164
#: tickets/serializers/ticket/apply_application.py:38 #: tickets/serializers/ticket/apply_application.py:38
#: tickets/serializers/ticket/common.py:59 #: tickets/serializers/ticket/common.py:59
#: xpack/plugins/change_auth_plan/serializers/asset.py:67 #: xpack/plugins/change_auth_plan/serializers/asset.py:67
@@ -634,7 +634,7 @@ msgid "Is active"
msgstr "アクティブです。" msgstr "アクティブです。"
#: assets/models/asset.py:222 assets/models/cluster.py:19 #: assets/models/asset.py:222 assets/models/cluster.py:19
#: assets/models/user.py:240 assets/models/user.py:395 #: assets/models/user.py:240 assets/models/user.py:406
msgid "Admin user" msgid "Admin user"
msgstr "管理ユーザー" msgstr "管理ユーザー"
@@ -650,7 +650,7 @@ msgstr "資産番号"
msgid "Labels" msgid "Labels"
msgstr "ラベル" msgstr "ラベル"
#: assets/models/asset.py:229 assets/models/base.py:183 #: assets/models/asset.py:229 assets/models/base.py:185
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:56 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:56
#: assets/models/cmd_filter.py:103 assets/models/group.py:21 #: assets/models/cmd_filter.py:103 assets/models/group.py:21
#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71 #: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71
@@ -786,32 +786,32 @@ msgstr "成功は"
msgid "Account backup execution" msgid "Account backup execution"
msgstr "アカウントバックアップの実行" msgstr "アカウントバックアップの実行"
#: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 #: assets/models/base.py:29 assets/tasks/const.py:51 audits/const.py:5
#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37
#: common/utils/ip/utils.py:84 #: common/utils/ip/utils.py:84
msgid "Unknown" msgid "Unknown"
msgstr "不明" msgstr "不明"
#: assets/models/base.py:31 #: assets/models/base.py:30
msgid "Ok" msgid "Ok"
msgstr "OK" msgstr "OK"
#: assets/models/base.py:32 audits/models.py:136 #: assets/models/base.py:31 audits/models.py:136
#: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/app.py:88
#: xpack/plugins/change_auth_plan/serializers/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:199
#: xpack/plugins/cloud/const.py:41 #: xpack/plugins/cloud/const.py:41
msgid "Failed" msgid "Failed"
msgstr "失敗しました" msgstr "失敗しました"
#: assets/models/base.py:38 assets/serializers/domain.py:47 #: assets/models/base.py:37 assets/serializers/domain.py:47
msgid "Connectivity" msgid "Connectivity"
msgstr "接続性" msgstr "接続性"
#: assets/models/base.py:40 authentication/models.py:263 #: assets/models/base.py:39 authentication/models.py:263
msgid "Date verified" msgid "Date verified"
msgstr "確認済みの日付" msgstr "確認済みの日付"
#: assets/models/base.py:177 assets/serializers/base.py:15 #: assets/models/base.py:179 assets/serializers/base.py:14
#: assets/serializers/base.py:37 assets/serializers/system_user.py:29 #: assets/serializers/base.py:37 assets/serializers/system_user.py:29
#: audits/signal_handlers.py:58 authentication/confirm/password.py:9 #: audits/signal_handlers.py:58 authentication/confirm/password.py:9
#: authentication/forms.py:32 #: authentication/forms.py:32
@@ -829,14 +829,14 @@ msgstr "確認済みの日付"
msgid "Password" msgid "Password"
msgstr "パスワード" msgstr "パスワード"
#: assets/models/base.py:178 assets/serializers/base.py:41 #: assets/models/base.py:180 assets/serializers/base.py:41
#: xpack/plugins/change_auth_plan/models/asset.py:53 #: xpack/plugins/change_auth_plan/models/asset.py:53
#: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:130
#: xpack/plugins/change_auth_plan/models/asset.py:206 #: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key" msgid "SSH private key"
msgstr "SSH秘密鍵" msgstr "SSH秘密鍵"
#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:56 #: assets/models/base.py:181 xpack/plugins/change_auth_plan/models/asset.py:56
#: xpack/plugins/change_auth_plan/models/asset.py:126 #: xpack/plugins/change_auth_plan/models/asset.py:126
#: xpack/plugins/change_auth_plan/models/asset.py:202 #: xpack/plugins/change_auth_plan/models/asset.py:202
msgid "SSH public key" msgid "SSH public key"
@@ -889,7 +889,7 @@ msgstr "デフォルトクラスター"
msgid "User group" msgid "User group"
msgstr "ユーザーグループ" msgstr "ユーザーグループ"
#: assets/models/cmd_filter.py:64 assets/serializers/system_user.py:59 #: assets/models/cmd_filter.py:64 assets/serializers/system_user.py:56
msgid "Command filter" msgid "Command filter"
msgstr "コマンドフィルター" msgstr "コマンドフィルター"
@@ -954,7 +954,7 @@ msgstr "テストゲートウェイ"
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:134 authentication/middleware.py:75 #: assets/models/domain.py:134 authentication/middleware.py:87
#: xpack/plugins/cloud/providers/fc.py:48 #: xpack/plugins/cloud/providers/fc.py:48
msgid "Authentication failed" msgid "Authentication failed"
msgstr "認証に失敗しました" msgstr "認証に失敗しました"
@@ -1015,7 +1015,7 @@ msgstr "フルバリュー"
msgid "Parent key" msgid "Parent key"
msgstr "親キー" msgstr "親キー"
#: assets/models/node.py:566 assets/serializers/system_user.py:267 #: assets/models/node.py:566 assets/serializers/system_user.py:263
#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:75 #: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:75
msgid "Node" msgid "Node"
msgstr "ノード" msgstr "ノード"
@@ -1095,7 +1095,7 @@ msgstr "ユーザースイッチ"
msgid "Switch from" msgid "Switch from"
msgstr "から切り替え" msgstr "から切り替え"
#: assets/models/user.py:345 #: assets/models/user.py:356
msgid "Can match system user" msgid "Can match system user"
msgstr "システムユーザーに一致できます" msgstr "システムユーザーに一致できます"
@@ -1192,7 +1192,7 @@ msgstr "ssh秘密鍵"
msgid "Key password" msgid "Key password"
msgstr "キーパスワード" msgstr "キーパスワード"
#: assets/serializers/base.py:58 #: assets/serializers/base.py:58 assets/serializers/utils.py:24
msgid "private key invalid or passphrase error" msgid "private key invalid or passphrase error"
msgstr "秘密鍵が無効またはpassphraseエラー" msgstr "秘密鍵が無効またはpassphraseエラー"
@@ -1205,7 +1205,7 @@ msgid "Pattern"
msgstr "パターン" msgstr "パターン"
#: assets/serializers/domain.py:14 assets/serializers/label.py:12 #: assets/serializers/domain.py:14 assets/serializers/label.py:12
#: assets/serializers/system_user.py:63 #: assets/serializers/system_user.py:60
#: perms/serializers/asset/permission.py:49 #: perms/serializers/asset/permission.py:49
msgid "Assets amount" msgid "Assets amount"
msgstr "資産額" msgstr "資産額"
@@ -1234,86 +1234,81 @@ msgstr "同じレベルのノード名を同じにすることはできません
msgid "SSH key fingerprint" msgid "SSH key fingerprint"
msgstr "SSHキー指紋" msgstr "SSHキー指紋"
#: assets/serializers/system_user.py:40 #: assets/serializers/system_user.py:59
#: perms/serializers/application/permission.py:46
msgid "Apps amount"
msgstr "アプリの量"
#: assets/serializers/system_user.py:62
#: perms/serializers/asset/permission.py:50 #: perms/serializers/asset/permission.py:50
msgid "Nodes amount" msgid "Nodes amount"
msgstr "ノード量" msgstr "ノード量"
#: assets/serializers/system_user.py:64 assets/serializers/system_user.py:269 #: assets/serializers/system_user.py:61 assets/serializers/system_user.py:265
msgid "Login mode display" msgid "Login mode display"
msgstr "ログインモード表示" msgstr "ログインモード表示"
#: assets/serializers/system_user.py:66 #: assets/serializers/system_user.py:63
msgid "Ad domain" msgid "Ad domain"
msgstr "広告ドメイン" msgstr "広告ドメイン"
#: assets/serializers/system_user.py:67 #: assets/serializers/system_user.py:64
msgid "Is asset protocol" msgid "Is asset protocol"
msgstr "資産プロトコルです" msgstr "資産プロトコルです"
#: assets/serializers/system_user.py:68 #: assets/serializers/system_user.py:65
msgid "Only ssh and automatic login system users are supported" msgid "Only ssh and automatic login system users are supported"
msgstr "sshと自動ログインシステムのユーザーのみがサポートされています" msgstr "sshと自動ログインシステムのユーザーのみがサポートされています"
#: assets/serializers/system_user.py:108 #: assets/serializers/system_user.py:105
msgid "Username same with user with protocol {} only allow 1" msgid "Username same with user with protocol {} only allow 1"
msgstr "プロトコル {} のユーザーと同じユーザー名は1のみ許可します" msgstr "プロトコル {} のユーザーと同じユーザー名は1のみ許可します"
#: assets/serializers/system_user.py:121 common/validators.py:14 #: assets/serializers/system_user.py:118 common/validators.py:14
msgid "Special char not allowed" msgid "Special char not allowed"
msgstr "特別なcharは許可されていません" msgstr "特別なcharは許可されていません"
#: assets/serializers/system_user.py:131 #: assets/serializers/system_user.py:128
msgid "* Automatic login mode must fill in the username." msgid "* Automatic login mode must fill in the username."
msgstr "* 自動ログインモードはユーザー名を入力する必要があります。" msgstr "* 自動ログインモードはユーザー名を入力する必要があります。"
#: assets/serializers/system_user.py:146 #: assets/serializers/system_user.py:143
msgid "Path should starts with /" msgid "Path should starts with /"
msgstr "パスは/で始まる必要があります" msgstr "パスは/で始まる必要があります"
#: assets/serializers/system_user.py:158 #: assets/serializers/system_user.py:155
msgid "Password or private key required" msgid "Password or private key required"
msgstr "パスワードまたは秘密鍵が必要" msgstr "パスワードまたは秘密鍵が必要"
#: assets/serializers/system_user.py:172 #: assets/serializers/system_user.py:169
msgid "Only ssh protocol system users are allowed" msgid "Only ssh protocol system users are allowed"
msgstr "Sshプロトコルシステムユーザーのみが許可されています" msgstr "Sshプロトコルシステムユーザーのみが許可されています"
#: assets/serializers/system_user.py:176 #: assets/serializers/system_user.py:173
msgid "The protocol must be consistent with the current user: {}" msgid "The protocol must be consistent with the current user: {}"
msgstr "プロトコルは現在のユーザーと一致している必要があります: {}" msgstr "プロトコルは現在のユーザーと一致している必要があります: {}"
#: assets/serializers/system_user.py:180 #: assets/serializers/system_user.py:177
msgid "Only system users with automatic login are allowed" msgid "Only system users with automatic login are allowed"
msgstr "自動ログインを持つシステムユーザーのみが許可されます" msgstr "自動ログインを持つシステムユーザーのみが許可されます"
#: assets/serializers/system_user.py:288 #: assets/serializers/system_user.py:284
msgid "System user name" msgid "System user name"
msgstr "システムユーザー名" msgstr "システムユーザー名"
#: assets/serializers/system_user.py:289 orgs/mixins/serializers.py:26 #: assets/serializers/system_user.py:285 orgs/mixins/serializers.py:26
#: rbac/serializers/rolebinding.py:23 #: rbac/serializers/rolebinding.py:23
msgid "Org name" msgid "Org name"
msgstr "組織名" msgstr "組織名"
#: assets/serializers/system_user.py:298 #: assets/serializers/system_user.py:294
msgid "Asset hostname" msgid "Asset hostname"
msgstr "資産ホスト名" msgstr "資産ホスト名"
#: assets/serializers/utils.py:11 #: assets/serializers/utils.py:13
msgid "Password can not contains `{{` " msgid "Password can not contains `{{` "
msgstr "パスワードには '{{' を含まない" msgstr "パスワードには '{{' を含まない"
#: assets/serializers/utils.py:14 #: assets/serializers/utils.py:16
msgid "Password can not contains `'` " msgid "Password can not contains `'` "
msgstr "パスワードには `'` を含まない" msgstr "パスワードには `'` を含まない"
#: assets/serializers/utils.py:16 #: assets/serializers/utils.py:18
msgid "Password can not contains `\"` " msgid "Password can not contains `\"` "
msgstr "パスワードには `\"` を含まない" msgstr "パスワードには `\"` を含まない"
@@ -1429,6 +1424,10 @@ msgid "For security, do not push user {}"
msgstr "セキュリティのために、ユーザー {} をプッシュしないでください" msgstr "セキュリティのために、ユーザー {} をプッシュしないでください"
#: assets/tasks/utils.py:55 #: assets/tasks/utils.py:55
msgid "Asset protocol not support system user protocol, skipped: {}"
msgstr "アセット プロトコルはシステム ユーザー プロトコルをサポートしていません。次をスキップします: {}"
#: assets/tasks/utils.py:59
msgid "No assets matched, stop task" msgid "No assets matched, stop task"
msgstr "一致する資産がない、タスクを停止" msgstr "一致する資産がない、タスクを停止"
@@ -1664,7 +1663,8 @@ msgstr "企業微信"
#: audits/signal_handlers.py:62 authentication/views/feishu.py:144 #: audits/signal_handlers.py:62 authentication/views/feishu.py:144
#: authentication/views/login.py:85 notifications/backends/__init__.py:14 #: authentication/views/login.py:85 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:10 users/models/user.py:736 #: settings/serializers/auth/feishu.py:10
#: settings/serializers/auth/feishu.py:13 users/models/user.py:736
msgid "FeiShu" msgid "FeiShu"
msgstr "本を飛ばす" msgstr "本を飛ばす"
@@ -1722,7 +1722,7 @@ msgid "Authentication"
msgstr "認証" msgstr "認証"
#: authentication/backends/custom.py:58 #: authentication/backends/custom.py:58
#: authentication/backends/oauth2/backends.py:158 authentication/models.py:158 #: authentication/backends/oauth2/backends.py:167 authentication/models.py:158
msgid "User invalid, disabled or expired" msgid "User invalid, disabled or expired"
msgstr "ユーザーが無効、無効、または期限切れです" msgstr "ユーザーが無効、無効、または期限切れです"
@@ -1937,15 +1937,15 @@ msgstr "本を飛ばすは拘束されていません"
msgid "Your password is invalid" msgid "Your password is invalid"
msgstr "パスワードが無効です" msgstr "パスワードが無効です"
#: authentication/errors/redirect.py:85 authentication/mixins.py:306 #: authentication/errors/redirect.py:85 authentication/mixins.py:307
msgid "Your password is too simple, please change it for security" msgid "Your password is too simple, please change it for security"
msgstr "パスワードがシンプルすぎるので、セキュリティのために変更してください" msgstr "パスワードがシンプルすぎるので、セキュリティのために変更してください"
#: authentication/errors/redirect.py:93 authentication/mixins.py:313 #: authentication/errors/redirect.py:93 authentication/mixins.py:314
msgid "You should to change your password before login" msgid "You should to change your password before login"
msgstr "ログインする前にパスワードを変更する必要があります" msgstr "ログインする前にパスワードを変更する必要があります"
#: authentication/errors/redirect.py:101 authentication/mixins.py:320 #: authentication/errors/redirect.py:101 authentication/mixins.py:321
msgid "Your password has expired, please reset before logging in" msgid "Your password has expired, please reset before logging in"
msgstr "" msgstr ""
"パスワードの有効期限が切れました。ログインする前にリセットしてください。" "パスワードの有効期限が切れました。ログインする前にリセットしてください。"
@@ -2044,15 +2044,15 @@ msgstr "電話番号を設定して有効にする"
msgid "Clear phone number to disable" msgid "Clear phone number to disable"
msgstr "無効にする電話番号をクリアする" msgstr "無効にする電話番号をクリアする"
#: authentication/middleware.py:76 settings/utils/ldap.py:652 #: authentication/middleware.py:88 settings/utils/ldap.py:652
msgid "Authentication failed (before login check failed): {}" msgid "Authentication failed (before login check failed): {}"
msgstr "認証に失敗しました (ログインチェックが失敗する前): {}" msgstr "認証に失敗しました (ログインチェックが失敗する前): {}"
#: authentication/mixins.py:256 #: authentication/mixins.py:257
msgid "The MFA type ({}) is not enabled" msgid "The MFA type ({}) is not enabled"
msgstr "MFAタイプ ({}) が有効になっていない" msgstr "MFAタイプ ({}) が有効になっていない"
#: authentication/mixins.py:296 #: authentication/mixins.py:297
msgid "Please change your password" msgid "Please change your password"
msgstr "パスワードを変更してください" msgstr "パスワードを変更してください"
@@ -2263,7 +2263,7 @@ msgstr "コードエラー"
#: authentication/templates/authentication/_msg_reset_password_code.html:9 #: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:413 ops/tasks.py:145 ops/tasks.py:148 #: jumpserver/conf.py:417 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33 #: tickets/templates/tickets/approve_check_password.html:33
@@ -2747,11 +2747,11 @@ msgstr "特殊文字を含むべきではない"
msgid "The mobile phone number format is incorrect" msgid "The mobile phone number format is incorrect"
msgstr "携帯電話番号の形式が正しくありません" msgstr "携帯電話番号の形式が正しくありません"
#: jumpserver/conf.py:412 #: jumpserver/conf.py:416
msgid "Create account successfully" msgid "Create account successfully"
msgstr "アカウントを正常に作成" msgstr "アカウントを正常に作成"
#: jumpserver/conf.py:414 #: jumpserver/conf.py:418
msgid "Your account has been created successfully" msgid "Your account has been created successfully"
msgstr "アカウントが正常に作成されました" msgstr "アカウントが正常に作成されました"
@@ -3198,6 +3198,10 @@ msgstr "ユーザーグループの量"
msgid "System users amount" msgid "System users amount"
msgstr "システムユーザー数" msgstr "システムユーザー数"
#: perms/serializers/application/permission.py:46
msgid "Apps amount"
msgstr "アプリの量"
#: perms/serializers/application/permission.py:79 #: perms/serializers/application/permission.py:79
msgid "" msgid ""
"The application list contains applications that are different from the " "The application list contains applications that are different from the "
@@ -3603,7 +3607,7 @@ msgstr "CAS"
msgid "Enable CAS Auth" msgid "Enable CAS Auth"
msgstr "CAS 認証の有効化" msgstr "CAS 認証の有効化"
#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 #: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:54
msgid "Server url" msgid "Server url"
msgstr "サービス側アドレス" msgstr "サービス側アドレス"
@@ -3636,7 +3640,7 @@ msgstr "そうでない場合はユーザーを作成"
msgid "Enable DingTalk Auth" msgid "Enable DingTalk Auth"
msgstr "ピン認証の有効化" msgstr "ピン認証の有効化"
#: settings/serializers/auth/feishu.py:14 #: settings/serializers/auth/feishu.py:16
msgid "Enable FeiShu Auth" msgid "Enable FeiShu Auth"
msgstr "飛本認証の有効化" msgstr "飛本認証の有効化"
@@ -3723,11 +3727,11 @@ msgstr "クライアントID"
msgid "Client Secret" msgid "Client Secret"
msgstr "クライアント秘密" msgstr "クライアント秘密"
#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 #: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:68
msgid "Provider auth endpoint" msgid "Provider auth endpoint"
msgstr "認証エンドポイントアドレス" msgstr "認証エンドポイントアドレス"
#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 #: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:71
msgid "Provider token endpoint" msgid "Provider token endpoint"
msgstr "プロバイダートークンエンドポイント" msgstr "プロバイダートークンエンドポイント"
@@ -3735,15 +3739,15 @@ msgstr "プロバイダートークンエンドポイント"
msgid "Client authentication method" msgid "Client authentication method"
msgstr "クライアント認証方式" msgstr "クライアント認証方式"
#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 #: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:77
msgid "Provider userinfo endpoint" msgid "Provider userinfo endpoint"
msgstr "プロバイダーuserinfoエンドポイント" msgstr "プロバイダーuserinfoエンドポイント"
#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 #: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:80
msgid "Provider end session endpoint" msgid "Provider end session endpoint"
msgstr "プロバイダーのセッション終了エンドポイント" msgstr "プロバイダーのセッション終了エンドポイント"
#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 #: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:98
#: settings/serializers/auth/saml2.py:35 #: settings/serializers/auth/saml2.py:35
msgid "Always update user" msgid "Always update user"
msgstr "常にユーザーを更新" msgstr "常にユーザーを更新"
@@ -3772,51 +3776,59 @@ msgstr ""
"ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピン" "ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピン"
"グする方法、username, name,emailはjumpserverのユーザーが必要とする属性です" "グする方法、username, name,emailはjumpserverのユーザーが必要とする属性です"
#: settings/serializers/auth/oidc.py:46 #: settings/serializers/auth/oidc.py:41
msgid "Enable PKCE"
msgstr "启启PKCE"
#: settings/serializers/auth/oidc.py:43
msgid "Code challenge method"
msgstr "Code暗号化方式です"
#: settings/serializers/auth/oidc.py:51
msgid "Use Keycloak" msgid "Use Keycloak"
msgstr "Keycloakを使用する" msgstr "Keycloakを使用する"
#: settings/serializers/auth/oidc.py:52 #: settings/serializers/auth/oidc.py:57
msgid "Realm name" msgid "Realm name"
msgstr "レルム名" msgstr "レルム名"
#: settings/serializers/auth/oidc.py:58 #: settings/serializers/auth/oidc.py:63
msgid "Enable OPENID Auth" msgid "Enable OPENID Auth"
msgstr "OIDC認証の有効化" msgstr "OIDC認証の有効化"
#: settings/serializers/auth/oidc.py:60 #: settings/serializers/auth/oidc.py:65
msgid "Provider endpoint" msgid "Provider endpoint"
msgstr "プロバイダーエンドポイント" msgstr "プロバイダーエンドポイント"
#: settings/serializers/auth/oidc.py:69 #: settings/serializers/auth/oidc.py:74
msgid "Provider jwks endpoint" msgid "Provider jwks endpoint"
msgstr "プロバイダーjwksエンドポイント" msgstr "プロバイダーjwksエンドポイント"
#: settings/serializers/auth/oidc.py:78 #: settings/serializers/auth/oidc.py:83
msgid "Provider sign alg" msgid "Provider sign alg"
msgstr "プロビダーサインalg" msgstr "プロビダーサインalg"
#: settings/serializers/auth/oidc.py:81 #: settings/serializers/auth/oidc.py:86
msgid "Provider sign key" msgid "Provider sign key"
msgstr "プロバイダ署名キー" msgstr "プロバイダ署名キー"
#: settings/serializers/auth/oidc.py:83 #: settings/serializers/auth/oidc.py:88
msgid "Scopes" msgid "Scopes"
msgstr "スコープ" msgstr "スコープ"
#: settings/serializers/auth/oidc.py:85 #: settings/serializers/auth/oidc.py:90
msgid "Id token max age" msgid "Id token max age"
msgstr "IDトークンの最大年齢" msgstr "IDトークンの最大年齢"
#: settings/serializers/auth/oidc.py:88 #: settings/serializers/auth/oidc.py:93
msgid "Id token include claims" msgid "Id token include claims"
msgstr "IDトークンにはクレームが含まれます" msgstr "IDトークンにはクレームが含まれます"
#: settings/serializers/auth/oidc.py:90 #: settings/serializers/auth/oidc.py:95
msgid "Use state" msgid "Use state"
msgstr "使用状態" msgstr "使用状態"
#: settings/serializers/auth/oidc.py:91 #: settings/serializers/auth/oidc.py:96
msgid "Use nonce" msgid "Use nonce"
msgstr "Nonceを使用" msgstr "Nonceを使用"
@@ -4731,8 +4743,8 @@ msgstr "期限切れです。"
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your password has expired, please click <a href=" " Your password has expired, please click <a "
"\"%(user_password_update_url)s\"> this link </a> update password.\n" "href=\"%(user_password_update_url)s\"> this link </a> update password.\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
@@ -4753,34 +4765,34 @@ msgid ""
" " " "
msgstr "" msgstr ""
"\n" "\n"
" クリックしてください <a href=\"%(user_password_update_url)s" " クリックしてください <a "
"\"> リンク </a> パスワードの更新\n" "href=\"%(user_password_update_url)s\"> リンク </a> パスワードの更新\n"
" " " "
#: templates/_message.html:43 #: templates/_message.html:43
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your information was incomplete. Please click <a href=" " Your information was incomplete. Please click <a "
"\"%(first_login_url)s\"> this link </a>to complete your information.\n" "href=\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
" あなたの情報が不完全なので、クリックしてください。 <a href=" " あなたの情報が不完全なので、クリックしてください。 <a "
"\"%(first_login_url)s\"> リンク </a> 補完\n" "href=\"%(first_login_url)s\"> リンク </a> 補完\n"
" " " "
#: templates/_message.html:56 #: templates/_message.html:56
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your ssh public key not set or expired. Please click <a href=" " Your ssh public key not set or expired. Please click <a "
"\"%(user_pubkey_update)s\"> this link </a>to update\n" "href=\"%(user_pubkey_update)s\"> this link </a>to update\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
" SSHキーが設定されていないか無効になっている場合は、 <a href=" " SSHキーが設定されていないか無効になっている場合は、 <a "
"\"%(user_pubkey_update)s\"> リンク </a> 更新\n" "href=\"%(user_pubkey_update)s\"> リンク </a> 更新\n"
" " " "
#: templates/_mfa_login_field.html:28 #: templates/_mfa_login_field.html:28
@@ -5000,7 +5012,7 @@ msgid "RDP Port"
msgstr "RDP ポート" msgstr "RDP ポート"
#: terminal/models/endpoint.py:27 terminal/models/endpoint.py:95 #: terminal/models/endpoint.py:27 terminal/models/endpoint.py:95
#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38 #: terminal/serializers/endpoint.py:61 terminal/serializers/storage.py:38
#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 #: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80
#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 #: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98
msgid "Endpoint" msgid "Endpoint"
@@ -5038,23 +5050,23 @@ msgstr "リプレイ"
msgid "Date end" msgid "Date end"
msgstr "終了日" msgstr "終了日"
#: terminal/models/session.py:261 #: terminal/models/session.py:265
msgid "Session record" msgid "Session record"
msgstr "セッション記録" msgstr "セッション記録"
#: terminal/models/session.py:263 #: terminal/models/session.py:267
msgid "Can monitor session" msgid "Can monitor session"
msgstr "セッションを監視できます" msgstr "セッションを監視できます"
#: terminal/models/session.py:264 #: terminal/models/session.py:268
msgid "Can share session" msgid "Can share session"
msgstr "セッションを共有できます" msgstr "セッションを共有できます"
#: terminal/models/session.py:265 #: terminal/models/session.py:269
msgid "Can terminate session" msgid "Can terminate session"
msgstr "セッションを終了できます" msgstr "セッションを終了できます"
#: terminal/models/session.py:266 #: terminal/models/session.py:270
msgid "Can validate session action perm" msgid "Can validate session action perm"
msgstr "セッションアクションのパーマを検証できます" msgstr "セッションアクションのパーマを検証できます"
@@ -5139,15 +5151,15 @@ msgstr "スレッド"
msgid "Boot Time" msgid "Boot Time"
msgstr "ブート時間" msgstr "ブート時間"
#: terminal/models/storage.py:29 #: terminal/models/storage.py:28
msgid "Default storage" msgid "Default storage"
msgstr "デフォルトのストレージ" msgstr "デフォルトのストレージ"
#: terminal/models/storage.py:139 terminal/models/terminal.py:109 #: terminal/models/storage.py:141 terminal/models/terminal.py:109
msgid "Command storage" msgid "Command storage"
msgstr "コマンドストレージ" msgstr "コマンドストレージ"
#: terminal/models/storage.py:199 terminal/models/terminal.py:110 #: terminal/models/storage.py:201 terminal/models/terminal.py:110
msgid "Replay storage" msgid "Replay storage"
msgstr "再生ストレージ" msgstr "再生ストレージ"
@@ -5191,15 +5203,15 @@ msgstr "レベル"
msgid "Batch danger command alert" msgid "Batch danger command alert"
msgstr "一括危険コマンド警告" msgstr "一括危険コマンド警告"
#: terminal/serializers/endpoint.py:14 #: terminal/serializers/endpoint.py:15
msgid "Magnus listen db port" msgid "Magnus listen db port"
msgstr "Magnus がリッスンするデータベース ポート" msgstr "Magnus がリッスンするデータベース ポート"
#: terminal/serializers/endpoint.py:17 #: terminal/serializers/endpoint.py:18
msgid "Magnus Listen port range" msgid "Magnus Listen port range"
msgstr "Magnus がリッスンするポート範囲" msgstr "Magnus がリッスンするポート範囲"
#: terminal/serializers/endpoint.py:19 #: terminal/serializers/endpoint.py:20
msgid "" msgid ""
"The range of ports that Magnus listens on is modified in the configuration " "The range of ports that Magnus listens on is modified in the configuration "
"file" "file"
@@ -5207,10 +5219,15 @@ msgstr "Magnus がリッスンするポート範囲を構成ファイルで変
#: terminal/serializers/endpoint.py:51 #: terminal/serializers/endpoint.py:51
msgid "" msgid ""
"If asset IP addresses under different endpoints conflict, use asset labels" "Format for comma-delimited string, with * indicating a match all. Such as: "
"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:"
"db8:1a:1110::/64<br>If asset IP addresses under different endpoints "
"conflict, use asset labels"
msgstr "" msgstr ""
"異なるエンドポイントの下に競合するアセットIPがある場合は、アセットタグを使用" "コンマ区切り文字列の形式。* はすべて一致することを示します。例: "
"して実装します" "192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:"
"db8:1a:1110:::/64<br>異なるエンドポイントの下に競合するアセットIPがある場合"
"は、アセットタグを使用して実装します"
#: terminal/serializers/session.py:15 terminal/serializers/session.py:42 #: terminal/serializers/session.py:15 terminal/serializers/session.py:42
msgid "Terminal display" msgid "Terminal display"
@@ -5334,7 +5351,7 @@ msgstr "見つかりません"
msgid "view" msgid "view"
msgstr "表示" msgstr "表示"
#: terminal/utils/db_port_mapper.py:65 #: terminal/utils/db_port_mapper.py:78
msgid "" msgid ""
"No available port is matched. The number of databases may have exceeded the " "No available port is matched. The number of databases may have exceeded the "
"number of ports open to the database agent service, Contact the " "number of ports open to the database agent service, Contact the "
@@ -5344,7 +5361,7 @@ msgstr ""
"サービスによって開かれたポートの数を超えた可能性があります。さらにポートを開" "サービスによって開かれたポートの数を超えた可能性があります。さらにポートを開"
"くには、管理者に連絡してください。" "くには、管理者に連絡してください。"
#: terminal/utils/db_port_mapper.py:91 #: terminal/utils/db_port_mapper.py:104
msgid "" msgid ""
"No ports can be used, check and modify the limit on the number of ports that " "No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file." "Magnus listens on in the configuration file."
@@ -5352,7 +5369,7 @@ msgstr ""
"使用できるポートがありません。設定ファイルで Magnus がリッスンするポート数の" "使用できるポートがありません。設定ファイルで Magnus がリッスンするポート数の"
"制限を確認して変更してください. " "制限を確認して変更してください. "
#: terminal/utils/db_port_mapper.py:93 #: terminal/utils/db_port_mapper.py:106
msgid "All available port count: {}, Already use port count: {}" msgid "All available port count: {}, Already use port count: {}"
msgstr "使用可能なすべてのポート数: {}、すでに使用しているポート数: {}" msgstr "使用可能なすべてのポート数: {}、すでに使用しているポート数: {}"
@@ -5444,19 +5461,19 @@ msgstr ""
"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: " "チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: "
"{}" "{}"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "Change field" msgid "Change field"
msgstr "フィールドを変更" msgstr "フィールドを変更"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "Before change" msgid "Before change"
msgstr "変更前" msgstr "変更前"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "After change" msgid "After change"
msgstr "変更後" msgstr "変更後"
#: tickets/handlers/base.py:96 #: tickets/handlers/base.py:98
msgid "{} {} the ticket" msgid "{} {} the ticket"
msgstr "{} {} チケット" msgstr "{} {} チケット"
@@ -7071,3 +7088,9 @@ msgstr "究極のエディション"
#: xpack/plugins/license/models.py:77 #: xpack/plugins/license/models.py:77
msgid "Community edition" msgid "Community edition"
msgstr "コミュニティ版" msgstr "コミュニティ版"
#~ msgid ""
#~ "If asset IP addresses under different endpoints conflict, use asset labels"
#~ msgstr ""
#~ "異なるエンドポイントの下に競合するアセットIPがある場合は、アセットタグを使"
#~ "用して実装します"

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b143c62843946c3e18b623d05065f12e9d3c578efe5cd0d2016056d2b8448ae8 oid sha256:62e47d2577f103b524a1ae75e3357cdeb5f90d89f89c58a8c8fa8f63d67749f8
size 109495 size 110097

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-11-17 17:34+0800\n" "POT-Creation-Date: 2023-05-08 18:10+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"
@@ -23,13 +23,13 @@ msgstr "访问控制"
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
#: applications/models/application.py:219 assets/models/asset.py:138 #: applications/models/application.py:219 assets/models/asset.py:138
#: assets/models/base.py:175 assets/models/cluster.py:18 #: assets/models/base.py:177 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:27 assets/models/domain.py:23 #: assets/models/cmd_filter.py:27 assets/models/domain.py:23
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 #: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29
#: settings/models.py:33 settings/serializers/sms.py:6 #: settings/models.py:33 settings/serializers/sms.py:6
#: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87 #: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87
#: terminal/models/storage.py:27 terminal/models/task.py:16 #: terminal/models/storage.py:26 terminal/models/task.py:16
#: terminal/models/terminal.py:101 users/forms/profile.py:33 #: terminal/models/terminal.py:101 users/forms/profile.py:33
#: users/models/group.py:15 users/models/user.py:673 #: users/models/group.py:15 users/models/user.py:673
#: xpack/plugins/cloud/models.py:27 #: xpack/plugins/cloud/models.py:27
@@ -54,14 +54,14 @@ msgstr "激活中"
#: acls/models/base.py:32 applications/models/application.py:232 #: acls/models/base.py:32 applications/models/application.py:232
#: assets/models/asset.py:143 assets/models/asset.py:231 #: assets/models/asset.py:143 assets/models/asset.py:231
#: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/backup.py:54 assets/models/base.py:182
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:52 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:52
#: assets/models/cmd_filter.py:100 assets/models/domain.py:24 #: assets/models/cmd_filter.py:100 assets/models/domain.py:24
#: assets/models/domain.py:65 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:73 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38
#: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97 #: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97
#: terminal/models/storage.py:30 terminal/models/terminal.py:115 #: terminal/models/storage.py:29 terminal/models/terminal.py:115
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
#: users/models/group.py:16 users/models/user.py:712 #: users/models/group.py:16 users/models/user.py:712
#: xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/change_auth_plan/models/base.py:44
@@ -130,7 +130,7 @@ msgstr "系统用户"
#: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/asset.py:386 assets/models/authbook.py:19
#: assets/models/backup.py:31 assets/models/cmd_filter.py:42 #: assets/models/backup.py:31 assets/models/cmd_filter.py:42
#: 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:40 #: assets/serializers/system_user.py:264 audits/models.py:40
#: authentication/models.py:66 authentication/models.py:90 #: authentication/models.py:66 authentication/models.py:90
#: 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:47 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:47
@@ -154,7 +154,7 @@ msgid "Format for comma-delimited string, with * indicating a match all. "
msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:178
#: assets/models/gathered_user.py:15 audits/models.py:139 #: assets/models/gathered_user.py:15 audits/models.py:139
#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260 #: authentication/models.py:260
@@ -184,7 +184,7 @@ msgstr ""
#: 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
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8
#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:54 #: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:58
msgid "IP" msgid "IP"
msgstr "IP" msgstr "IP"
@@ -264,7 +264,7 @@ 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:46 assets/models/user.py:343 audits/models.py:41 #: assets/models/cmd_filter.py:46 assets/models/user.py:354 audits/models.py:41
#: authentication/models.py:83 perms/models/application_permission.py:33 #: authentication/models.py:83 perms/models/application_permission.py:33
#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22
#: terminal/backends/command/serializers.py:36 terminal/models/session.py:49 #: terminal/backends/command/serializers.py:36 terminal/models/session.py:49
@@ -275,7 +275,7 @@ msgid "System user"
msgstr "系统用户" msgstr "系统用户"
#: applications/models/account.py:17 assets/models/authbook.py:21 #: applications/models/account.py:17 assets/models/authbook.py:21
#: settings/serializers/auth/cas.py:20 #: settings/serializers/auth/cas.py:20 settings/serializers/auth/feishu.py:20
msgid "Version" msgid "Version"
msgstr "版本" msgstr "版本"
@@ -305,7 +305,7 @@ msgstr "类别"
#: assets/models/cmd_filter.py:86 assets/models/user.py:251 #: assets/models/cmd_filter.py:86 assets/models/user.py:251
#: authentication/models.py:70 perms/models/application_permission.py:24 #: authentication/models.py:70 perms/models/application_permission.py:24
#: perms/serializers/application/user_permission.py:34 #: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:59 terminal/models/storage.py:145 #: terminal/models/storage.py:58 terminal/models/storage.py:147
#: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/comment.py:26 tickets/models/flow.py:57
#: tickets/models/ticket/apply_application.py:18 #: tickets/models/ticket/apply_application.py:18
#: tickets/models/ticket/general.py:273 #: tickets/models/ticket/general.py:273
@@ -348,7 +348,7 @@ msgid "Type display"
msgstr "类型名称" msgstr "类型名称"
#: applications/serializers/application.py:105 assets/models/asset.py:230 #: applications/serializers/application.py:105 assets/models/asset.py:230
#: assets/models/base.py:181 assets/models/cluster.py:26 #: assets/models/base.py:183 assets/models/cluster.py:26
#: assets/models/cmd_filter.py:53 assets/models/domain.py:26 #: assets/models/cmd_filter.py:53 assets/models/domain.py:26
#: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/account.py:18 #: assets/models/label.py:25 assets/serializers/account.py:18
@@ -362,7 +362,7 @@ msgstr "类型名称"
msgid "Date created" msgid "Date created"
msgstr "创建日期" msgstr "创建日期"
#: applications/serializers/application.py:106 assets/models/base.py:182 #: applications/serializers/application.py:106 assets/models/base.py:184
#: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20 #: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20
#: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29 #: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29
#: assets/serializers/cmd_filter.py:49 common/db/models.py:117 #: assets/serializers/cmd_filter.py:49 common/db/models.py:117
@@ -440,7 +440,7 @@ msgid "Application path"
msgstr "应用路径" msgstr "应用路径"
#: applications/serializers/attrs/application_category/remote_app.py:44 #: applications/serializers/attrs/application_category/remote_app.py:44
#: assets/serializers/system_user.py:167 #: assets/serializers/system_user.py:164
#: tickets/serializers/ticket/apply_application.py:38 #: tickets/serializers/ticket/apply_application.py:38
#: tickets/serializers/ticket/common.py:59 #: tickets/serializers/ticket/common.py:59
#: xpack/plugins/change_auth_plan/serializers/asset.py:67 #: xpack/plugins/change_auth_plan/serializers/asset.py:67
@@ -627,7 +627,7 @@ msgid "Is active"
msgstr "激活" msgstr "激活"
#: assets/models/asset.py:222 assets/models/cluster.py:19 #: assets/models/asset.py:222 assets/models/cluster.py:19
#: assets/models/user.py:240 assets/models/user.py:395 #: assets/models/user.py:240 assets/models/user.py:406
msgid "Admin user" msgid "Admin user"
msgstr "特权用户" msgstr "特权用户"
@@ -643,7 +643,7 @@ msgstr "资产编号"
msgid "Labels" msgid "Labels"
msgstr "标签管理" msgstr "标签管理"
#: assets/models/asset.py:229 assets/models/base.py:183 #: assets/models/asset.py:229 assets/models/base.py:185
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:56 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:56
#: assets/models/cmd_filter.py:103 assets/models/group.py:21 #: assets/models/cmd_filter.py:103 assets/models/group.py:21
#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71 #: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71
@@ -779,32 +779,32 @@ msgstr "是否成功"
msgid "Account backup execution" msgid "Account backup execution"
msgstr "账号备份执行" msgstr "账号备份执行"
#: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 #: assets/models/base.py:29 assets/tasks/const.py:51 audits/const.py:5
#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37
#: common/utils/ip/utils.py:84 #: common/utils/ip/utils.py:84
msgid "Unknown" msgid "Unknown"
msgstr "未知" msgstr "未知"
#: assets/models/base.py:31 #: assets/models/base.py:30
msgid "Ok" msgid "Ok"
msgstr "成功" msgstr "成功"
#: assets/models/base.py:32 audits/models.py:136 #: assets/models/base.py:31 audits/models.py:136
#: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/app.py:88
#: xpack/plugins/change_auth_plan/serializers/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:199
#: xpack/plugins/cloud/const.py:41 #: xpack/plugins/cloud/const.py:41
msgid "Failed" msgid "Failed"
msgstr "失败" msgstr "失败"
#: assets/models/base.py:38 assets/serializers/domain.py:47 #: assets/models/base.py:37 assets/serializers/domain.py:47
msgid "Connectivity" msgid "Connectivity"
msgstr "可连接性" msgstr "可连接性"
#: assets/models/base.py:40 authentication/models.py:263 #: assets/models/base.py:39 authentication/models.py:263
msgid "Date verified" msgid "Date verified"
msgstr "校验日期" msgstr "校验日期"
#: assets/models/base.py:177 assets/serializers/base.py:15 #: assets/models/base.py:179 assets/serializers/base.py:14
#: assets/serializers/base.py:37 assets/serializers/system_user.py:29 #: assets/serializers/base.py:37 assets/serializers/system_user.py:29
#: audits/signal_handlers.py:58 authentication/confirm/password.py:9 #: audits/signal_handlers.py:58 authentication/confirm/password.py:9
#: authentication/forms.py:32 #: authentication/forms.py:32
@@ -822,14 +822,14 @@ msgstr "校验日期"
msgid "Password" msgid "Password"
msgstr "密码" msgstr "密码"
#: assets/models/base.py:178 assets/serializers/base.py:41 #: assets/models/base.py:180 assets/serializers/base.py:41
#: xpack/plugins/change_auth_plan/models/asset.py:53 #: xpack/plugins/change_auth_plan/models/asset.py:53
#: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:130
#: xpack/plugins/change_auth_plan/models/asset.py:206 #: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key" msgid "SSH private key"
msgstr "SSH密钥" msgstr "SSH密钥"
#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:56 #: assets/models/base.py:181 xpack/plugins/change_auth_plan/models/asset.py:56
#: xpack/plugins/change_auth_plan/models/asset.py:126 #: xpack/plugins/change_auth_plan/models/asset.py:126
#: xpack/plugins/change_auth_plan/models/asset.py:202 #: xpack/plugins/change_auth_plan/models/asset.py:202
msgid "SSH public key" msgid "SSH public key"
@@ -882,7 +882,7 @@ msgstr "默认Cluster"
msgid "User group" msgid "User group"
msgstr "用户组" msgstr "用户组"
#: assets/models/cmd_filter.py:64 assets/serializers/system_user.py:59 #: assets/models/cmd_filter.py:64 assets/serializers/system_user.py:56
msgid "Command filter" msgid "Command filter"
msgstr "命令过滤器" msgstr "命令过滤器"
@@ -947,7 +947,7 @@ msgstr "测试网关"
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:134 authentication/middleware.py:75 #: assets/models/domain.py:134 authentication/middleware.py:87
#: xpack/plugins/cloud/providers/fc.py:48 #: xpack/plugins/cloud/providers/fc.py:48
msgid "Authentication failed" msgid "Authentication failed"
msgstr "认证失败" msgstr "认证失败"
@@ -1008,7 +1008,7 @@ msgstr "全称"
msgid "Parent key" msgid "Parent key"
msgstr "ssh私钥" msgstr "ssh私钥"
#: assets/models/node.py:566 assets/serializers/system_user.py:267 #: assets/models/node.py:566 assets/serializers/system_user.py:263
#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:75 #: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:75
msgid "Node" msgid "Node"
msgstr "节点" msgstr "节点"
@@ -1088,7 +1088,7 @@ msgstr "用户切换"
msgid "Switch from" msgid "Switch from"
msgstr "切换自" msgstr "切换自"
#: assets/models/user.py:345 #: assets/models/user.py:356
msgid "Can match system user" msgid "Can match system user"
msgstr "可以匹配系统用户" msgstr "可以匹配系统用户"
@@ -1182,7 +1182,7 @@ msgstr "ssh私钥"
msgid "Key password" msgid "Key password"
msgstr "密钥密码" msgstr "密钥密码"
#: assets/serializers/base.py:58 #: assets/serializers/base.py:58 assets/serializers/utils.py:24
msgid "private key invalid or passphrase error" msgid "private key invalid or passphrase error"
msgstr "密钥不合法或密钥密码错误" msgstr "密钥不合法或密钥密码错误"
@@ -1195,7 +1195,7 @@ msgid "Pattern"
msgstr "模式" msgstr "模式"
#: assets/serializers/domain.py:14 assets/serializers/label.py:12 #: assets/serializers/domain.py:14 assets/serializers/label.py:12
#: assets/serializers/system_user.py:63 #: assets/serializers/system_user.py:60
#: perms/serializers/asset/permission.py:49 #: perms/serializers/asset/permission.py:49
msgid "Assets amount" msgid "Assets amount"
msgstr "资产数量" msgstr "资产数量"
@@ -1224,86 +1224,81 @@ msgstr "同级别节点名字不能重复"
msgid "SSH key fingerprint" msgid "SSH key fingerprint"
msgstr "密钥指纹" msgstr "密钥指纹"
#: assets/serializers/system_user.py:40 #: assets/serializers/system_user.py:59
#: perms/serializers/application/permission.py:46
msgid "Apps amount"
msgstr "应用数量"
#: assets/serializers/system_user.py:62
#: perms/serializers/asset/permission.py:50 #: perms/serializers/asset/permission.py:50
msgid "Nodes amount" msgid "Nodes amount"
msgstr "节点数量" msgstr "节点数量"
#: assets/serializers/system_user.py:64 assets/serializers/system_user.py:269 #: assets/serializers/system_user.py:61 assets/serializers/system_user.py:265
msgid "Login mode display" msgid "Login mode display"
msgstr "认证方式名称" msgstr "认证方式名称"
#: assets/serializers/system_user.py:66 #: assets/serializers/system_user.py:63
msgid "Ad domain" msgid "Ad domain"
msgstr "Ad 网域" msgstr "Ad 网域"
#: assets/serializers/system_user.py:67 #: assets/serializers/system_user.py:64
msgid "Is asset protocol" msgid "Is asset protocol"
msgstr "资产协议" msgstr "资产协议"
#: assets/serializers/system_user.py:68 #: assets/serializers/system_user.py:65
msgid "Only ssh and automatic login system users are supported" msgid "Only ssh and automatic login system users are supported"
msgstr "仅支持ssh协议和自动登录的系统用户" msgstr "仅支持ssh协议和自动登录的系统用户"
#: assets/serializers/system_user.py:108 #: assets/serializers/system_user.py:105
msgid "Username same with user with protocol {} only allow 1" msgid "Username same with user with protocol {} only allow 1"
msgstr "用户名和用户相同的一种协议只允许存在一个" msgstr "用户名和用户相同的一种协议只允许存在一个"
#: assets/serializers/system_user.py:121 common/validators.py:14 #: assets/serializers/system_user.py:118 common/validators.py:14
msgid "Special char not allowed" msgid "Special char not allowed"
msgstr "不能包含特殊字符" msgstr "不能包含特殊字符"
#: assets/serializers/system_user.py:131 #: assets/serializers/system_user.py:128
msgid "* Automatic login mode must fill in the username." msgid "* Automatic login mode must fill in the username."
msgstr "自动登录模式,必须填写用户名" msgstr "自动登录模式,必须填写用户名"
#: assets/serializers/system_user.py:146 #: assets/serializers/system_user.py:143
msgid "Path should starts with /" msgid "Path should starts with /"
msgstr "路径应该以 / 开头" msgstr "路径应该以 / 开头"
#: assets/serializers/system_user.py:158 #: assets/serializers/system_user.py:155
msgid "Password or private key required" msgid "Password or private key required"
msgstr "密码或密钥密码需要一个" msgstr "密码或密钥密码需要一个"
#: assets/serializers/system_user.py:172 #: assets/serializers/system_user.py:169
msgid "Only ssh protocol system users are allowed" msgid "Only ssh protocol system users are allowed"
msgstr "仅允许ssh协议的系统用户" msgstr "仅允许ssh协议的系统用户"
#: assets/serializers/system_user.py:176 #: assets/serializers/system_user.py:173
msgid "The protocol must be consistent with the current user: {}" msgid "The protocol must be consistent with the current user: {}"
msgstr "协议必须和当前用户保持一致: {}" msgstr "协议必须和当前用户保持一致: {}"
#: assets/serializers/system_user.py:180 #: assets/serializers/system_user.py:177
msgid "Only system users with automatic login are allowed" msgid "Only system users with automatic login are allowed"
msgstr "仅允许自动登录的系统用户" msgstr "仅允许自动登录的系统用户"
#: assets/serializers/system_user.py:288 #: assets/serializers/system_user.py:284
msgid "System user name" msgid "System user name"
msgstr "系统用户名称" msgstr "系统用户名称"
#: assets/serializers/system_user.py:289 orgs/mixins/serializers.py:26 #: assets/serializers/system_user.py:285 orgs/mixins/serializers.py:26
#: rbac/serializers/rolebinding.py:23 #: rbac/serializers/rolebinding.py:23
msgid "Org name" msgid "Org name"
msgstr "组织名称" msgstr "组织名称"
#: assets/serializers/system_user.py:298 #: assets/serializers/system_user.py:294
msgid "Asset hostname" msgid "Asset hostname"
msgstr "资产主机名" msgstr "资产主机名"
#: assets/serializers/utils.py:11 #: assets/serializers/utils.py:13
msgid "Password can not contains `{{` " msgid "Password can not contains `{{` "
msgstr "密码不能包含 `{{` 字符" msgstr "密码不能包含 `{{` 字符"
#: assets/serializers/utils.py:14 #: assets/serializers/utils.py:16
msgid "Password can not contains `'` " msgid "Password can not contains `'` "
msgstr "密码不能包含 `'` 字符" msgstr "密码不能包含 `'` 字符"
#: assets/serializers/utils.py:16 #: assets/serializers/utils.py:18
msgid "Password can not contains `\"` " msgid "Password can not contains `\"` "
msgstr "密码不能包含 `\"` 字符" msgstr "密码不能包含 `\"` 字符"
@@ -1415,6 +1410,10 @@ msgid "For security, do not push user {}"
msgstr "为了安全,禁止推送用户 {}" msgstr "为了安全,禁止推送用户 {}"
#: assets/tasks/utils.py:55 #: assets/tasks/utils.py:55
msgid "Asset protocol not support system user protocol, skipped: {}"
msgstr "资产协议不支持系统用户协议,跳过: {}"
#: assets/tasks/utils.py:59
msgid "No assets matched, stop task" msgid "No assets matched, stop task"
msgstr "没有匹配到资产,结束任务" msgstr "没有匹配到资产,结束任务"
@@ -1650,7 +1649,8 @@ msgstr "企业微信"
#: audits/signal_handlers.py:62 authentication/views/feishu.py:144 #: audits/signal_handlers.py:62 authentication/views/feishu.py:144
#: authentication/views/login.py:85 notifications/backends/__init__.py:14 #: authentication/views/login.py:85 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:10 users/models/user.py:736 #: settings/serializers/auth/feishu.py:10
#: settings/serializers/auth/feishu.py:13 users/models/user.py:736
msgid "FeiShu" msgid "FeiShu"
msgstr "飞书" msgstr "飞书"
@@ -1706,7 +1706,7 @@ msgid "Authentication"
msgstr "认证" msgstr "认证"
#: authentication/backends/custom.py:58 #: authentication/backends/custom.py:58
#: authentication/backends/oauth2/backends.py:158 authentication/models.py:158 #: authentication/backends/oauth2/backends.py:167 authentication/models.py:158
msgid "User invalid, disabled or expired" msgid "User invalid, disabled or expired"
msgstr "用户无效,已禁用或已过期" msgstr "用户无效,已禁用或已过期"
@@ -1913,15 +1913,15 @@ msgstr "没有绑定飞书"
msgid "Your password is invalid" msgid "Your password is invalid"
msgstr "您的密码无效" msgstr "您的密码无效"
#: authentication/errors/redirect.py:85 authentication/mixins.py:306 #: authentication/errors/redirect.py:85 authentication/mixins.py:307
msgid "Your password is too simple, please change it for security" msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改" msgstr "你的密码过于简单,为了安全,请修改"
#: authentication/errors/redirect.py:93 authentication/mixins.py:313 #: authentication/errors/redirect.py:93 authentication/mixins.py:314
msgid "You should to change your password before login" msgid "You should to change your password before login"
msgstr "登录完成前,请先修改密码" msgstr "登录完成前,请先修改密码"
#: authentication/errors/redirect.py:101 authentication/mixins.py:320 #: authentication/errors/redirect.py:101 authentication/mixins.py:321
msgid "Your password has expired, please reset before logging in" msgid "Your password has expired, please reset before logging in"
msgstr "您的密码已过期,先修改再登录" msgstr "您的密码已过期,先修改再登录"
@@ -2018,15 +2018,15 @@ msgstr "设置手机号码启用"
msgid "Clear phone number to disable" msgid "Clear phone number to disable"
msgstr "清空手机号码禁用" msgstr "清空手机号码禁用"
#: authentication/middleware.py:76 settings/utils/ldap.py:652 #: authentication/middleware.py:88 settings/utils/ldap.py:652
msgid "Authentication failed (before login check failed): {}" msgid "Authentication failed (before login check failed): {}"
msgstr "认证失败(登录前检查失败): {}" msgstr "认证失败(登录前检查失败): {}"
#: authentication/mixins.py:256 #: authentication/mixins.py:257
msgid "The MFA type ({}) is not enabled" msgid "The MFA type ({}) is not enabled"
msgstr "该 MFA ({}) 方式没有启用" msgstr "该 MFA ({}) 方式没有启用"
#: authentication/mixins.py:296 #: authentication/mixins.py:297
msgid "Please change your password" msgid "Please change your password"
msgstr "请修改密码" msgstr "请修改密码"
@@ -2233,7 +2233,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_reset_password_code.html:9 #: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:413 ops/tasks.py:145 ops/tasks.py:148 #: jumpserver/conf.py:417 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33 #: tickets/templates/tickets/approve_check_password.html:33
@@ -2708,11 +2708,11 @@ msgstr "不能包含特殊字符"
msgid "The mobile phone number format is incorrect" msgid "The mobile phone number format is incorrect"
msgstr "手机号格式不正确" msgstr "手机号格式不正确"
#: jumpserver/conf.py:412 #: jumpserver/conf.py:416
msgid "Create account successfully" msgid "Create account successfully"
msgstr "创建账号成功" msgstr "创建账号成功"
#: jumpserver/conf.py:414 #: jumpserver/conf.py:418
msgid "Your account has been created successfully" msgid "Your account has been created successfully"
msgstr "你的账号已创建成功" msgstr "你的账号已创建成功"
@@ -3153,6 +3153,10 @@ msgstr "用户组数量"
msgid "System users amount" msgid "System users amount"
msgstr "系统用户数量" msgstr "系统用户数量"
#: perms/serializers/application/permission.py:46
msgid "Apps amount"
msgstr "应用数量"
#: perms/serializers/application/permission.py:79 #: perms/serializers/application/permission.py:79
msgid "" msgid ""
"The application list contains applications that are different from the " "The application list contains applications that are different from the "
@@ -3555,7 +3559,7 @@ msgstr "CAS"
msgid "Enable CAS Auth" msgid "Enable CAS Auth"
msgstr "启用 CAS 认证" msgstr "启用 CAS 认证"
#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 #: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:54
msgid "Server url" msgid "Server url"
msgstr "服务端地址" msgstr "服务端地址"
@@ -3588,7 +3592,7 @@ msgstr "创建用户(如果不存在)"
msgid "Enable DingTalk Auth" msgid "Enable DingTalk Auth"
msgstr "启用钉钉认证" msgstr "启用钉钉认证"
#: settings/serializers/auth/feishu.py:14 #: settings/serializers/auth/feishu.py:16
msgid "Enable FeiShu Auth" msgid "Enable FeiShu Auth"
msgstr "启用飞书认证" msgstr "启用飞书认证"
@@ -3675,11 +3679,11 @@ msgstr "客户端 ID"
msgid "Client Secret" msgid "Client Secret"
msgstr "客户端密钥" msgstr "客户端密钥"
#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 #: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:68
msgid "Provider auth endpoint" msgid "Provider auth endpoint"
msgstr "授权端点地址" msgstr "授权端点地址"
#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 #: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:71
msgid "Provider token endpoint" msgid "Provider token endpoint"
msgstr "token 端点地址" msgstr "token 端点地址"
@@ -3687,15 +3691,15 @@ msgstr "token 端点地址"
msgid "Client authentication method" msgid "Client authentication method"
msgstr "客户端认证方式" msgstr "客户端认证方式"
#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 #: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:77
msgid "Provider userinfo endpoint" msgid "Provider userinfo endpoint"
msgstr "用户信息端点地址" msgstr "用户信息端点地址"
#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 #: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:80
msgid "Provider end session endpoint" msgid "Provider end session endpoint"
msgstr "注销会话端点地址" msgstr "注销会话端点地址"
#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 #: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:98
#: settings/serializers/auth/saml2.py:35 #: settings/serializers/auth/saml2.py:35
msgid "Always update user" msgid "Always update user"
msgstr "总是更新用户信息" msgstr "总是更新用户信息"
@@ -3724,51 +3728,59 @@ msgstr ""
"用户属性映射代表怎样将OpenID中用户属性映射到jumpserver用户上username, name," "用户属性映射代表怎样将OpenID中用户属性映射到jumpserver用户上username, name,"
"email 是jumpserver的用户需要属性" "email 是jumpserver的用户需要属性"
#: settings/serializers/auth/oidc.py:46 #: settings/serializers/auth/oidc.py:41
msgid "Enable PKCE"
msgstr "启用 PKCE"
#: settings/serializers/auth/oidc.py:43
msgid "Code challenge method"
msgstr "Code加密方式"
#: settings/serializers/auth/oidc.py:51
msgid "Use Keycloak" msgid "Use Keycloak"
msgstr "使用 Keycloak" msgstr "使用 Keycloak"
#: settings/serializers/auth/oidc.py:52 #: settings/serializers/auth/oidc.py:57
msgid "Realm name" msgid "Realm name"
msgstr "域" msgstr "域"
#: settings/serializers/auth/oidc.py:58 #: settings/serializers/auth/oidc.py:63
msgid "Enable OPENID Auth" msgid "Enable OPENID Auth"
msgstr "启用 OIDC 认证" msgstr "启用 OIDC 认证"
#: settings/serializers/auth/oidc.py:60 #: settings/serializers/auth/oidc.py:65
msgid "Provider endpoint" msgid "Provider endpoint"
msgstr "端点地址" msgstr "端点地址"
#: settings/serializers/auth/oidc.py:69 #: settings/serializers/auth/oidc.py:74
msgid "Provider jwks endpoint" msgid "Provider jwks endpoint"
msgstr "jwks 端点地址" msgstr "jwks 端点地址"
#: settings/serializers/auth/oidc.py:78 #: settings/serializers/auth/oidc.py:83
msgid "Provider sign alg" msgid "Provider sign alg"
msgstr "签名算法" msgstr "签名算法"
#: settings/serializers/auth/oidc.py:81 #: settings/serializers/auth/oidc.py:86
msgid "Provider sign key" msgid "Provider sign key"
msgstr "签名 Key" msgstr "签名 Key"
#: settings/serializers/auth/oidc.py:83 #: settings/serializers/auth/oidc.py:88
msgid "Scopes" msgid "Scopes"
msgstr "连接范围" msgstr "连接范围"
#: settings/serializers/auth/oidc.py:85 #: settings/serializers/auth/oidc.py:90
msgid "Id token max age" msgid "Id token max age"
msgstr "令牌有效时间" msgstr "令牌有效时间"
#: settings/serializers/auth/oidc.py:88 #: settings/serializers/auth/oidc.py:93
msgid "Id token include claims" msgid "Id token include claims"
msgstr "声明" msgstr "声明"
#: settings/serializers/auth/oidc.py:90 #: settings/serializers/auth/oidc.py:95
msgid "Use state" msgid "Use state"
msgstr "使用状态" msgstr "使用状态"
#: settings/serializers/auth/oidc.py:91 #: settings/serializers/auth/oidc.py:96
msgid "Use nonce" msgid "Use nonce"
msgstr "临时使用" msgstr "临时使用"
@@ -4652,13 +4664,13 @@ msgstr "过期。"
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your password has expired, please click <a href=" " Your password has expired, please click <a "
"\"%(user_password_update_url)s\"> this link </a> update password.\n" "href=\"%(user_password_update_url)s\"> this link </a> update password.\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
" 您的密码已经过期,请点击 <a href=" " 您的密码已经过期,请点击 <a "
"\"%(user_password_update_url)s\"> 链接 </a> 更新密码\n" "href=\"%(user_password_update_url)s\"> 链接 </a> 更新密码\n"
" " " "
#: templates/_message.html:30 #: templates/_message.html:30
@@ -4682,8 +4694,8 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your information was incomplete. Please click <a href=" " Your information was incomplete. Please click <a "
"\"%(first_login_url)s\"> this link </a>to complete your information.\n" "href=\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
@@ -4695,13 +4707,13 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your ssh public key not set or expired. Please click <a href=" " Your ssh public key not set or expired. Please click <a "
"\"%(user_pubkey_update)s\"> this link </a>to update\n" "href=\"%(user_pubkey_update)s\"> this link </a>to update\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
" 您的SSH密钥没有设置或已失效请点击 <a href=" " 您的SSH密钥没有设置或已失效请点击 <a "
"\"%(user_pubkey_update)s\"> 链接 </a> 更新\n" "href=\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" " " "
#: templates/_mfa_login_field.html:28 #: templates/_mfa_login_field.html:28
@@ -4914,7 +4926,7 @@ msgid "RDP Port"
msgstr "RDP 端口" msgstr "RDP 端口"
#: terminal/models/endpoint.py:27 terminal/models/endpoint.py:95 #: terminal/models/endpoint.py:27 terminal/models/endpoint.py:95
#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38 #: terminal/serializers/endpoint.py:61 terminal/serializers/storage.py:38
#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 #: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80
#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 #: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98
msgid "Endpoint" msgid "Endpoint"
@@ -4952,23 +4964,23 @@ msgstr "回放"
msgid "Date end" msgid "Date end"
msgstr "结束日期" msgstr "结束日期"
#: terminal/models/session.py:261 #: terminal/models/session.py:265
msgid "Session record" msgid "Session record"
msgstr "会话记录" msgstr "会话记录"
#: terminal/models/session.py:263 #: terminal/models/session.py:267
msgid "Can monitor session" msgid "Can monitor session"
msgstr "可以监控会话" msgstr "可以监控会话"
#: terminal/models/session.py:264 #: terminal/models/session.py:268
msgid "Can share session" msgid "Can share session"
msgstr "可以分享会话" msgstr "可以分享会话"
#: terminal/models/session.py:265 #: terminal/models/session.py:269
msgid "Can terminate session" msgid "Can terminate session"
msgstr "可以终断会话" msgstr "可以终断会话"
#: terminal/models/session.py:266 #: terminal/models/session.py:270
msgid "Can validate session action perm" msgid "Can validate session action perm"
msgstr "可以验证会话动作权限" msgstr "可以验证会话动作权限"
@@ -5053,15 +5065,15 @@ msgstr "线程数"
msgid "Boot Time" msgid "Boot Time"
msgstr "运行时间" msgstr "运行时间"
#: terminal/models/storage.py:29 #: terminal/models/storage.py:28
msgid "Default storage" msgid "Default storage"
msgstr "默认存储" msgstr "默认存储"
#: terminal/models/storage.py:139 terminal/models/terminal.py:109 #: terminal/models/storage.py:141 terminal/models/terminal.py:109
msgid "Command storage" msgid "Command storage"
msgstr "命令存储" msgstr "命令存储"
#: terminal/models/storage.py:199 terminal/models/terminal.py:110 #: terminal/models/storage.py:201 terminal/models/terminal.py:110
msgid "Replay storage" msgid "Replay storage"
msgstr "录像存储" msgstr "录像存储"
@@ -5105,15 +5117,15 @@ msgstr "级别"
msgid "Batch danger command alert" msgid "Batch danger command alert"
msgstr "批量危险命令告警" msgstr "批量危险命令告警"
#: terminal/serializers/endpoint.py:14 #: terminal/serializers/endpoint.py:15
msgid "Magnus listen db port" msgid "Magnus listen db port"
msgstr "Magnus 监听的数据库端口" msgstr "Magnus 监听的数据库端口"
#: terminal/serializers/endpoint.py:17 #: terminal/serializers/endpoint.py:18
msgid "Magnus Listen port range" msgid "Magnus Listen port range"
msgstr "Magnus 监听的端口范围" msgstr "Magnus 监听的端口范围"
#: terminal/serializers/endpoint.py:19 #: terminal/serializers/endpoint.py:20
msgid "" msgid ""
"The range of ports that Magnus listens on is modified in the configuration " "The range of ports that Magnus listens on is modified in the configuration "
"file" "file"
@@ -5121,8 +5133,14 @@ msgstr "请在配置文件中修改 Magnus 监听的端口范围"
#: terminal/serializers/endpoint.py:51 #: terminal/serializers/endpoint.py:51
msgid "" msgid ""
"If asset IP addresses under different endpoints conflict, use asset labels" "Format for comma-delimited string, with * indicating a match all. Such as: "
msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现" "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:"
"db8:1a:1110::/64<br>If asset IP addresses under different endpoints "
"conflict, use asset labels"
msgstr ""
"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, "
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64<br>如果不同端点"
"下的资产 IP 有冲突,使用资产标签实现"
#: terminal/serializers/session.py:15 terminal/serializers/session.py:42 #: terminal/serializers/session.py:15 terminal/serializers/session.py:42
msgid "Terminal display" msgid "Terminal display"
@@ -5246,7 +5264,7 @@ msgstr "没有发现"
msgid "view" msgid "view"
msgstr "查看" msgstr "查看"
#: terminal/utils/db_port_mapper.py:65 #: terminal/utils/db_port_mapper.py:78
msgid "" msgid ""
"No available port is matched. The number of databases may have exceeded the " "No available port is matched. The number of databases may have exceeded the "
"number of ports open to the database agent service, Contact the " "number of ports open to the database agent service, Contact the "
@@ -5255,13 +5273,13 @@ msgstr ""
"未匹配到可用端口,数据库的数量可能已经超过数据库代理服务开放的端口数量,请联" "未匹配到可用端口,数据库的数量可能已经超过数据库代理服务开放的端口数量,请联"
"系管理员开放更多端口。" "系管理员开放更多端口。"
#: terminal/utils/db_port_mapper.py:91 #: terminal/utils/db_port_mapper.py:104
msgid "" msgid ""
"No ports can be used, check and modify the limit on the number of ports that " "No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file." "Magnus listens on in the configuration file."
msgstr "没有端口可以使用,检查并修改配置文件中 Magnus 监听的端口数量限制。" msgstr "没有端口可以使用,检查并修改配置文件中 Magnus 监听的端口数量限制。"
#: terminal/utils/db_port_mapper.py:93 #: terminal/utils/db_port_mapper.py:106
msgid "All available port count: {}, Already use port count: {}" msgid "All available port count: {}, Already use port count: {}"
msgstr "所有可用端口数量:{},已使用端口数量:{}" msgstr "所有可用端口数量:{},已使用端口数量:{}"
@@ -5351,19 +5369,19 @@ msgid ""
msgstr "" msgstr ""
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" "通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "Change field" msgid "Change field"
msgstr "变更字段" msgstr "变更字段"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "Before change" msgid "Before change"
msgstr "变更前" msgstr "变更前"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "After change" msgid "After change"
msgstr "变更后" msgstr "变更后"
#: tickets/handlers/base.py:96 #: tickets/handlers/base.py:98
msgid "{} {} the ticket" msgid "{} {} the ticket"
msgstr "{} {} 工单" msgstr "{} {} 工单"
@@ -6961,3 +6979,7 @@ msgstr "旗舰版"
#: xpack/plugins/license/models.py:77 #: xpack/plugins/license/models.py:77
msgid "Community edition" msgid "Community edition"
msgstr "社区版" msgstr "社区版"
#~ msgid ""
#~ "If asset IP addresses under different endpoints conflict, use asset labels"
#~ msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现"

View File

@@ -82,7 +82,7 @@ class CeleryResultApi(generics.RetrieveAPIView):
def get_object(self): def get_object(self):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
return AsyncResult(pk) return AsyncResult(str(pk))
class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):

View File

@@ -2,9 +2,9 @@
# #
from django.conf import settings from django.conf import settings
from .ansible.inventory import BaseInventory
from common.utils import get_logger from common.utils import get_logger
from .ansible.inventory import BaseInventory
__all__ = [ __all__ = [
'JMSInventory', 'JMSCustomInventory', 'JMSInventory', 'JMSCustomInventory',
@@ -110,7 +110,10 @@ class JMSInventory(JMSBaseInventory):
if self.system_user: if self.system_user:
self.system_user.load_asset_special_auth(asset=asset, username=self.run_as) self.system_user.load_asset_special_auth(asset=asset, username=self.run_as)
return self.system_user._to_secret_json() info = self.system_user._to_secret_json()
if self.run_as:
info['username'] = self.run_as
return info
else: else:
return {} return {}

View File

@@ -9,7 +9,13 @@ __all__ = ['FeiShuSettingSerializer']
class FeiShuSettingSerializer(serializers.Serializer): class FeiShuSettingSerializer(serializers.Serializer):
PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('FeiShu')) PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('FeiShu'))
VERSION_CHOICES = (
('feishu', _('FeiShu')),
('lark', 'Lark')
)
AUTH_FEISHU = serializers.BooleanField(default=False, label=_('Enable FeiShu Auth'))
FEISHU_APP_ID = serializers.CharField(max_length=256, required=True, label='App ID') FEISHU_APP_ID = serializers.CharField(max_length=256, required=True, label='App ID')
FEISHU_APP_SECRET = EncryptedField(max_length=256, required=False, label='App Secret') FEISHU_APP_SECRET = EncryptedField(max_length=256, required=False, label='App Secret')
AUTH_FEISHU = serializers.BooleanField(default=False, label=_('Enable FeiShu Auth')) FEISHU_VERSION = serializers.ChoiceField(
choices=VERSION_CHOICES, default='feishu', label=_('Version')
)

View File

@@ -38,6 +38,11 @@ class CommonSettingSerializer(serializers.Serializer):
help_text=_('User attr map present how to map OpenID user attr to ' help_text=_('User attr map present how to map OpenID user attr to '
'jumpserver, username,name,email is jumpserver attr') 'jumpserver, username,name,email is jumpserver attr')
) )
AUTH_OPENID_PKCE = serializers.BooleanField(required=False, label=_('Enable PKCE'))
AUTH_OPENID_CODE_CHALLENGE_METHOD = serializers.ChoiceField(
default='S256', label=_('Code challenge method'),
choices=(('S256', 'HS256'), ('plain', 'Plain'))
)
class KeycloakSettingSerializer(CommonSettingSerializer): class KeycloakSettingSerializer(CommonSettingSerializer):

View File

@@ -79,7 +79,7 @@ class Endpoint(JMSModel):
return None return None
endpoints = cls.objects.filter(name__in=values).order_by('-date_updated') endpoints = cls.objects.filter(name__in=values).order_by('-date_updated')
for endpoint in endpoints: for endpoint in endpoints:
if endpoint.is_valid_for(protocol): if endpoint.is_valid_for(instance, protocol):
return endpoint return endpoint

View File

@@ -118,9 +118,13 @@ class Session(OrgModelMixin):
def find_ok_relative_path_in_storage(self, storage): def find_ok_relative_path_in_storage(self, storage):
session_paths = self.get_all_possible_relative_path() session_paths = self.get_all_possible_relative_path()
for rel_path in session_paths: for rel_path in session_paths:
if storage.exists(rel_path): # storage 为多个外部存储时, 可能会因部分不可用,
return rel_path # 抛出异常, 影响录像的获取
try:
if storage.exists(rel_path):
return rel_path
except:
pass
@property @property
def asset_obj(self): def asset_obj(self):
return Asset.objects.get(id=self.asset_id) return Asset.objects.get(id=self.asset_id)

View File

@@ -19,7 +19,6 @@ from .terminal import Terminal
from .command import Command from .command import Command
from .. import const from .. import const
logger = get_logger(__file__) logger = get_logger(__file__)
@@ -37,10 +36,10 @@ class CommonStorageModelMixin(models.Model):
def set_to_default(self): def set_to_default(self):
self.is_default = True self.is_default = True
self.save() self.save(update_fields=['is_default'])
self.__class__.objects.select_for_update()\ self.__class__.objects.select_for_update() \
.filter(is_default=True)\ .filter(is_default=True) \
.exclude(id=self.id)\ .exclude(id=self.id) \
.update(is_default=False) .update(is_default=False)
@classmethod @classmethod
@@ -128,7 +127,10 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin):
def save(self, force_insert=False, force_update=False, using=None, def save(self, force_insert=False, force_update=False, using=None,
update_fields=None): update_fields=None):
super().save() super().save(
force_insert=force_insert, force_update=force_update,
using=using, update_fields=update_fields
)
if self.type in TYPE_ENGINE_MAPPING: if self.type in TYPE_ENGINE_MAPPING:
engine_mod = import_module(TYPE_ENGINE_MAPPING[self.type]) engine_mod = import_module(TYPE_ENGINE_MAPPING[self.type])

View File

@@ -1,6 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.drf.serializers import BulkModelSerializer from common.drf.serializers import BulkModelSerializer
from common.utils import lazyproperty
from acls.serializers.rules import ip_group_child_validator, ip_group_help_text from acls.serializers.rules import ip_group_child_validator, ip_group_help_text
from ..utils import db_port_manager from ..utils import db_port_manager
from ..models import Endpoint, EndpointRule from ..models import Endpoint, EndpointRule
@@ -46,9 +47,12 @@ class EndpointSerializer(BulkModelSerializer):
class EndpointRuleSerializer(BulkModelSerializer): class EndpointRuleSerializer(BulkModelSerializer):
_ip_group_help_text = '{} <br> {}'.format( _ip_group_help_text = _(
ip_group_help_text, 'Format for comma-delimited string, with * indicating a match all. '
_('If asset IP addresses under different endpoints conflict, use asset labels') 'Such as: '
'192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64'
'<br>'
'If asset IP addresses under different endpoints conflict, use asset labels'
) )
ip_group = serializers.ListField( ip_group = serializers.ListField(
default=['*'], label=_('IP'), help_text=_ip_group_help_text, default=['*'], label=_('IP'), help_text=_ip_group_help_text,

View File

@@ -16,10 +16,10 @@ logger = get_logger(__file__)
@receiver(django_ready) @receiver(django_ready)
def init_db_port_mapper(sender, **kwargs): def check_db_port_mapper(sender, **kwargs):
logger.info('Init db port mapper') logger.info('Init db port mapper')
try: try:
db_port_manager.init() db_port_manager.check()
except (ProgrammingError,) as e: except (ProgrammingError,) as e:
pass pass

View File

@@ -35,9 +35,22 @@ class DBPortManager(object):
def magnus_listen_port_range(self): def magnus_listen_port_range(self):
return settings.MAGNUS_PORTS return settings.MAGNUS_PORTS
def init(self): @staticmethod
def fetch_dbs():
with tmp_to_root_org(): with tmp_to_root_org():
db_ids = Application.objects.filter(category=AppCategory.db).values_list('id', flat=True) dbs = Application.objects.filter(category=AppCategory.db)
return dbs
def check(self):
dbs = self.fetch_dbs()
for db in dbs:
port = self.get_port_by_db(db, raise_exception=False)
if not port:
self.add(db)
def init(self):
dbs = self.fetch_dbs()
db_ids = dbs.values_list('id', flat=True)
db_ids = [str(i) for i in db_ids] db_ids = [str(i) for i in db_ids]
mapper = dict(zip(self.all_available_ports, list(db_ids))) mapper = dict(zip(self.all_available_ports, list(db_ids)))
self.set_mapper(mapper) self.set_mapper(mapper)

View File

@@ -1,12 +1,13 @@
from django.utils.translation import ugettext as _ import html2text
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from common.utils import get_logger from common.utils import get_logger
from tickets.const import TicketState, TicketType
from tickets.utils import ( from tickets.utils import (
send_ticket_processed_mail_to_applicant, send_ticket_processed_mail_to_applicant,
send_ticket_applied_mail_to_assignees send_ticket_applied_mail_to_assignees
) )
from tickets.const import TicketState, TicketType
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -96,11 +97,20 @@ class BaseHandler:
approve_info = _('{} {} the ticket').format(user_display, state_display) approve_info = _('{} {} the ticket').format(user_display, state_display)
context = self._diff_prev_approve_context(state) context = self._diff_prev_approve_context(state)
context.update({'approve_info': approve_info}) context.update({'approve_info': approve_info})
body = self.safe_html_script(
render_to_string('tickets/ticket_approve_diff.html', context)
)
data = { data = {
'body': render_to_string('tickets/ticket_approve_diff.html', context), 'body': body,
'user': user, 'user': user,
'user_display': str(user), 'user_display': str(user),
'type': 'state', 'type': 'state',
'state': state 'state': state
} }
return self.ticket.comments.create(**data) return self.ticket.comments.create(**data)
@staticmethod
def safe_html_script(unsafe_html):
unsafe_html = unsafe_html.replace('\n', '')
html_str = html2text.html2text(unsafe_html)
return html_str

View File

@@ -18,7 +18,6 @@
{% endfor %} {% endfor %}
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
</div> </div>
{% endif %} {% endif %}

View File

@@ -4,6 +4,7 @@
from celery import shared_task from celery import shared_task
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.db import transaction
from users.notifications import PasswordExpirationReminderMsg from users.notifications import PasswordExpirationReminderMsg
from ops.celery.utils import ( from ops.celery.utils import (
@@ -80,6 +81,7 @@ def check_user_expired_periodic():
@shared_task @shared_task
@transaction.atomic
def import_ldap_user(): def import_ldap_user():
logger.info("Start import ldap user task") logger.info("Start import ldap user task")
util_server = LDAPServerUtil() util_server = LDAPServerUtil()

View File

@@ -63,7 +63,7 @@ jsonfield2==4.0.0.post0
geoip2==4.5.0 geoip2==4.5.0
ipip-ipdb==1.6.1 ipip-ipdb==1.6.1
# Django environment # Django environment
Django==3.2.16 Django==3.2.15
django-bootstrap3==14.2.0 django-bootstrap3==14.2.0
django-filter==2.4.0 django-filter==2.4.0
django-formtools==2.2 django-formtools==2.2

View File

@@ -26,7 +26,7 @@ connection_params = {
if settings.REDIS_USE_SSL: if settings.REDIS_USE_SSL:
connection_params['ssl'] = settings.REDIS_USE_SSL connection_params['ssl'] = settings.REDIS_USE_SSL
connection_params['ssl_cert_reqs'] = settings.REDIS_SSL_REQUIRED connection_params['ssl_cert_reqs'] = settings.REDIS_SSL_REQUIRED
connection_params['ssl_keyfile'] = settings.REDIS_SSL_KEY connection_params['ssl_keyfile'] = settings.REDIS_SSL_KEY
connection_params['ssl_certfile'] = settings.REDIS_SSL_CERT connection_params['ssl_certfile'] = settings.REDIS_SSL_CERT
connection_params['ssl_ca_certs'] = settings.REDIS_SSL_CA connection_params['ssl_ca_certs'] = settings.REDIS_SSL_CA
@@ -39,6 +39,11 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
connection_params['sentinels'] = REDIS_SENTINELS connection_params['sentinels'] = REDIS_SENTINELS
sentinel_client = Sentinel( sentinel_client = Sentinel(
**connection_params, sentinel_kwargs={ **connection_params, sentinel_kwargs={
'ssl': settings.REDIS_USE_SSL,
'ssl_cert_reqs': settings.REDIS_SSL_REQUIRED,
'ssl_keyfile': settings.REDIS_SSL_KEY,
'ssl_certfile': settings.REDIS_SSL_CERT,
'ssl_ca_certs': settings.REDIS_SSL_CA,
'password': REDIS_SENTINEL_PASSWORD, 'password': REDIS_SENTINEL_PASSWORD,
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT 'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT
} }