Compare commits

...

52 Commits

Author SHA1 Message Date
fit2bot
068e079279 feat: Update v2.28.15 2023-06-08 18:46:34 +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
51 changed files with 828 additions and 518 deletions

4
Dockerfile-ee Normal file
View File

@@ -0,0 +1,4 @@
ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
FROM jumpserver/core:${VERSION}
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack

View File

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

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
4e4e58480fc061c2e487c64f0f70667e22eaef27

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 common.drf.serializers import BulkModelSerializer
from common.drf.serializers import MethodSerializer

View File

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

View File

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

View File

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

View File

@@ -4,16 +4,16 @@
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.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 .base import BaseUser
from common.utils import signer, get_object_or_none, is_uuid
from .asset import Asset
from .authbook import AuthBook
from .base import BaseUser
__all__ = ['AdminUser', 'SystemUser']
logger = logging.getLogger(__name__)
@@ -187,22 +187,22 @@ class AuthMixin:
if username == '':
username = self.username
authbook = AuthBook.objects.filter(
asset=asset, username=username, systemuser__isnull=True
).order_by('-date_created').first()
not_stu_query = Q(asset=asset, username=username, systemuser__isnull=True)
stu_query = Q(asset=asset, systemuser=self)
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:
authbook = AuthBook.objects.filter(
asset=asset, systemuser=self
).order_by('-date_created').first()
authbook = stu_qs.first()
if not authbook:
return None
authbook.load_auth()
self.password = authbook.password
self.private_key = authbook.private_key
self.public_key = authbook.public_key
self.password = authbook.password or self.password or ''
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):
from users.models import User
@@ -249,12 +249,19 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser):
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
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'))
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)])
protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol'))
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)]
)
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'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
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'))
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')
)
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
token = models.TextField(default='', verbose_name=_('Token'))
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)
# linux su 命令 (switch user)
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):
username = self.username
@@ -322,9 +331,20 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser):
assets = Asset.objects.filter(id__in=asset_ids)
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):
self.assets.add(*tuple(assets_or_ids))
self.add_related_assets_to_su_from_if_need(assets_or_ids)
assets = self.filter_contain_protocol_assets(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):
if self.protocol not in [self.Protocol.ssh.value]:

View File

@@ -1,24 +1,24 @@
# -*- coding: utf-8 -*-
#
from io import StringIO
from django.utils.translation import ugettext_lazy as _
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 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
class AuthSerializer(serializers.ModelSerializer):
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):
if private_key is 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
def save(self, **kwargs):
@@ -57,10 +57,7 @@ class AuthSerializerMixin(serializers.ModelSerializer):
if not valid:
raise serializers.ValidationError(_("private key invalid or passphrase error"))
private_key = ssh_private_key_gen(private_key, password=passphrase)
string_io = StringIO()
private_key.write_private_key(string_io)
private_key = string_io.getvalue()
private_key = parse_ssh_private_key_str(private_key, password=passphrase)
return private_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.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.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 orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser, Asset
from .utils import validate_password_for_ansible
from .base import AuthSerializerMixin
from .utils import validate_password_for_ansible
from ..models import SystemUser, Asset
__all__ = [
'SystemUserSerializer', 'MiniSystemUserSerializer',
@@ -36,9 +36,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
token = EncryptedField(
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:
model = SystemUser
@@ -53,7 +50,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'su_enabled', 'su_from',
'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
extra_kwargs = {
'cmd_filters': {"required": False, 'label': _('Command filter')},
@@ -214,7 +211,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
elif attrs.get('private_key'):
private_key = attrs['private_key']
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
return attrs
@@ -241,7 +238,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset \
.annotate(assets_amount=Count("assets")) \
.prefetch_related('nodes', 'cmd_filters')
return queryset

View File

@@ -1,6 +1,8 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
def validate_password_for_ansible(password):
""" 校验 Ansible 不支持的特殊字符 """
@@ -15,3 +17,9 @@ def validate_password_for_ansible(password):
if '"' in password:
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:
if not check_asset_can_run_ansible(asset):
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)
if not cleaned_assets:
logger.info(_("No assets matched, stop task"))

View File

@@ -2,13 +2,14 @@ import csv
import codecs
from itertools import chain
from datetime import datetime
from django.http import HttpResponse
from django.db import models
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.db import fields
from .const import DEFAULT_CITY
@@ -78,6 +79,8 @@ def model_to_dict_for_operate_log(
f.verbose_name = 'id'
elif isinstance(value, list):
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):
data[str(f.verbose_name)] = value

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
#
import requests
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model
from django.utils.http import urlencode
from django.conf import settings
@@ -90,8 +91,12 @@ class OAuth2Backend(JMSModelBackend):
request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME)
)
}
access_token_url = '{url}?{query}'.format(
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, query=urlencode(query_dict)
if '?' in settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT:
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()
requests_func = getattr(requests, token_method, requests.get)
@@ -114,12 +119,16 @@ class OAuth2Backend(JMSModelBackend):
headers = {
'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'))
userinfo_url = '{url}?{query}'.format(
url=settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT,
if '?' in 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)
)
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,
separator=separator,
query=urlencode(query_dict)
)
logger.debug(log_prompt.format('Redirect login url'))

View File

@@ -88,7 +88,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
"""
@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. """
log_prompt = "Process authenticate [OIDCAuthCodeBackend]: {}"
logger.debug(log_prompt.format('start'))
@@ -134,6 +134,8 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
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':
token_payload.update({
'client_id': settings.AUTH_OPENID_CLIENT_ID,

View File

@@ -9,7 +9,10 @@
"""
import base64
import hashlib
import time
import secrets
from django.conf import settings
from django.contrib import auth
@@ -38,6 +41,19 @@ class OIDCAuthRequestView(View):
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):
""" 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
# authentication request and the callback.
if settings.AUTH_OPENID_USE_STATE:
@@ -138,8 +164,9 @@ class OIDCAuthCallbackView(View):
# Authenticates the end-user.
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'))
user = auth.authenticate(nonce=nonce, request=request)
user = auth.authenticate(nonce=nonce, request=request, code_verifier=code_verifier)
if user and user.is_valid:
logger.debug(log_prompt.format('Login: {}'.format(user)))
auth.login(self.request, user)

View File

@@ -61,6 +61,18 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
return response
if not request.session.get('auth_third_party_required'):
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)
try:
self.request = request
@@ -88,7 +100,6 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
guard_url = "%s?%s" % (guard_url, args)
response = redirect(guard_url)
finally:
request.session.pop('auth_third_party_required', '')
return response

View File

@@ -212,7 +212,8 @@ class MFAMixin:
self._do_check_user_mfa(code, mfa_type, user=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
if not user.mfa_enabled:
return
@@ -220,14 +221,16 @@ class MFAMixin:
active_mfa_names = user.active_mfa_backends_mapper.keys()
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_username'] = user.username
self.request.session['auth_mfa_time'] = time.time()
self.request.session['auth_mfa_required'] = 0
self.request.session['auth_mfa_type'] = mfa_type
MFABlockUtils(user.username, self.get_request_ip()).clean_failed_count()
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:
self.request.session.pop(k, '')
@@ -262,7 +265,7 @@ class MFAMixin:
ok, msg = mfa_backend.check_code(code)
if ok:
self.mark_mfa_ok(mfa_type)
self.mark_mfa_ok(mfa_type, user)
return
raise errors.MFAFailedError(
@@ -369,7 +372,7 @@ class AuthACLMixin:
def check_user_login_confirm(self):
ticket = self.get_ticket()
if not ticket:
raise errors.LoginConfirmOtherError('', "Not found")
raise errors.LoginConfirmOtherError('', "Not found", '')
elif ticket.is_state(ticket.State.approved):
self.request.session["auth_confirm_required"] = ''
return
@@ -512,4 +515,20 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost
args = self.request.META.get('QUERY_STRING', '')
if 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,
'redirect_uri': redirect_uri,
}
url = URL.AUTHEN + '?' + urlencode(params)
url = URL().authen + '?' + urlencode(params)
return url
@staticmethod

View File

@@ -32,11 +32,14 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
return super().get(*args, **kwargs)
def form_valid(self, form):
from users.utils import MFABlockUtils
code = form.cleaned_data.get('code')
mfa_type = form.cleaned_data.get('mfa_type')
try:
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')
except (errors.MFAFailedError, errors.BlockMFAError) as e:
form.add_error('code', e.msg)

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import json
from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import APIException
from django.conf import settings
from common.utils.common import get_logger
from common.sdk.im.utils import digest
from common.sdk.im.mixin import RequestMixin, BaseRequest
@@ -11,14 +12,30 @@ logger = get_logger(__name__)
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
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:
@@ -51,7 +68,7 @@ class FeishuRequests(BaseRequest):
def request_access_token(self):
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)
access_token = response['tenant_access_token']
@@ -86,7 +103,7 @@ class FeiShu(RequestMixin):
'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)
return data['data']['user_id']
@@ -107,7 +124,7 @@ class FeiShu(RequestMixin):
try:
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:
# 只处理可预知的错误
logger.exception(e)

View File

@@ -1,24 +1,23 @@
# -*- coding: utf-8 -*-
#
import re
import json
from six import string_types
import base64
import os
import time
import hashlib
import json
import os
import re
import time
from io import StringIO
from itertools import chain
import paramiko
import sshpubkeys
from cryptography.hazmat.primitives import serialization
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from itsdangerous import (
TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer,
BadSignature, SignatureExpired
)
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.fields.files import FileField
from six import string_types
from .http import http_date
@@ -69,22 +68,25 @@ class Signer(metaclass=Singleton):
return None
_supported_paramiko_ssh_key_types = (
paramiko.RSAKey,
paramiko.DSSKey,
paramiko.Ed25519Key,
paramiko.ECDSAKey,
)
def ssh_key_string_to_obj(text, password=None):
key = None
try:
key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
else:
return key
try:
key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
else:
return key
for ssh_key_type in _supported_paramiko_ssh_key_types:
if not issubclass(ssh_key_type, paramiko.PKey):
continue
try:
key = ssh_key_type.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
else:
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):
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')
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):
if isinstance(text, bytes):
try:
text = text.decode("utf-8")
except UnicodeDecodeError:
return False
key = parse_ssh_private_key_str(text, password=password)
return bool(key)
key = ssh_key_string_to_obj(text, password=password)
if key is None:
return False
else:
return True
def parse_ssh_private_key_str(text: bytes, password=None) -> str:
private_key = _parse_ssh_private_key(text, password=password)
if private_key is None:
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):
@@ -226,4 +276,4 @@ def ensure_last_char_is_ascii(data):
def data_to_json(data, sort_keys=True, indent=2, cls=None):
if cls is None:
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)))
def random_string(length, lower=True, upper=True, digit=True, special_char=False):
chars = string.ascii_letters
if digit:
chars += string.digits
def random_string(length: int, lower=True, upper=True, digit=True, special_char=False):
args_names = ['lower', 'upper', 'digit', 'special_char']
args_values = [lower, upper, digit, special_char]
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:
password = list(random.choice(chars) for i in range(length))
if upper and not any(c.upper() for c in password):
continue
if lower and not any(c.lower() for c in password):
continue
if digit and not any(c.isdigit() for c in password):
continue
break
if special_char:
spc = random.choice(string_punctuation)
i = random.choice(range(1, len(password)))
password[i] = spc
for k, v in kwargs.items():
if v and not (set(password) & set(args_string_map[k])):
# 没有包含指定的字符, retry
break
else:
if not can_startswith_special_char and password[0] in args_string_map['special_char']:
# 首位不能为特殊字符, retry
continue
else:
# 满足要求终止 while 循环
break
password = ''.join(password)
return password

View File

@@ -202,6 +202,7 @@ class Config(dict):
'REDIS_SSL_KEY': None,
'REDIS_SSL_CERT': None,
'REDIS_SSL_CA': None,
'REDIS_SSL_REQUIRED': 'none',
# Redis Sentinel
'REDIS_SENTINEL_HOSTS': '',
'REDIS_SENTINEL_PASSWORD': '',
@@ -269,6 +270,8 @@ class Config(dict):
'AUTH_OPENID_USER_ATTR_MAP': {
'name': 'name', 'username': 'preferred_username', 'email': 'email'
},
'AUTH_OPENID_PKCE': False,
'AUTH_OPENID_CODE_CHALLENGE_METHOD': 'S256',
# OpenID 新配置参数 (version >= 1.5.9)
'AUTH_OPENID_PROVIDER_ENDPOINT': 'https://oidc.example.com/',
@@ -373,6 +376,7 @@ class Config(dict):
'AUTH_FEISHU': False,
'FEISHU_APP_ID': '',
'FEISHU_APP_SECRET': '',
'FEISHU_VERSION': 'feishu',
'LOGIN_REDIRECT_TO_BACKEND': '', # 'OPENID / CAS / SAML2
'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_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER
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_CALLBACK_URL_NAME = 'authentication:openid:login-callback'
AUTH_OPENID_AUTH_LOGOUT_URL_NAME = 'authentication:openid:logout'
@@ -135,6 +137,7 @@ DINGTALK_APPSECRET = CONFIG.DINGTALK_APPSECRET
AUTH_FEISHU = CONFIG.AUTH_FEISHU
FEISHU_APP_ID = CONFIG.FEISHU_APP_ID
FEISHU_APP_SECRET = CONFIG.FEISHU_APP_SECRET
FEISHU_VERSION = CONFIG.FEISHU_VERSION
# Saml2 auth
AUTH_SAML2 = CONFIG.AUTH_SAML2

View File

@@ -1,6 +1,9 @@
import os
import platform
from redis.sentinel import SentinelManagedSSLConnection
if platform.system() == 'Darwin' and platform.machine() == 'arm64':
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
if CONFIG.DB_ENGINE.lower() == 'mysql':
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',
'SENTINELS': REDIS_SENTINELS, 'PASSWORD': CONFIG.REDIS_PASSWORD,
'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,
'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'
else:
REDIS_LOCATION_NO_DB = '%(protocol)s://:%(password)s@%(host)s:%(port)s/{}' % {

View File

@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
#
import os
import ssl
from urllib.parse import urlencode
from .base import (
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
)
from ..const import CONFIG, PROJECT_DIR
@@ -81,41 +81,54 @@ BOOTSTRAP3 = {
}
# Django channels support websocket
if not REDIS_USE_SSL:
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 = {
REDIS_LAYERS_HOST = {
'db': CONFIG.REDIS_DB_WS,
'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:
REDIS_HOST['sentinels'] = REDIS_SENTINELS
REDIS_HOST['master_name'] = REDIS_SENTINEL_SERVICE_NAME
REDIS_HOST['sentinel_kwargs'] = {
REDIS_LAYERS_HOST['sentinels'] = REDIS_SENTINELS
REDIS_LAYERS_HOST['master_name'] = REDIS_SENTINEL_SERVICE_NAME
REDIS_LAYERS_HOST['sentinel_kwargs'] = {
'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:
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 = {
'default': {
'BACKEND': 'common.cache.RedisChannelLayer',
'CONFIG': {
"hosts": [REDIS_HOST],
"hosts": [REDIS_LAYERS_HOST],
},
},
}
ASGI_APPLICATION = 'jumpserver.routing.application'
# Dump all celery log to here
@@ -132,13 +145,18 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
'master_name': REDIS_SENTINEL_SERVICE_NAME,
'sentinel_kwargs': {
'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
else:
CELERY_BROKER_URL = CELERY_BROKER_URL_FORMAT % {
'protocol': 'rediss' if REDIS_USE_SSL else 'redis',
'protocol': REDIS_PROTOCOL,
'password': CONFIG.REDIS_PASSWORD,
'host': CONFIG.REDIS_HOST,
'port': CONFIG.REDIS_PORT,

View File

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

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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
#: 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/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
#: settings/models.py:33 settings/serializers/sms.py:6
#: 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
#: users/models/group.py:15 users/models/user.py:673
#: xpack/plugins/cloud/models.py:27
@@ -55,14 +55,14 @@ msgstr "アクティブ"
#: acls/models/base.py:32 applications/models/application.py:232
#: 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/cmd_filter.py:100 assets/models/domain.py:24
#: assets/models/domain.py:65 assets/models/group.py:23
#: 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
#: 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
#: users/models/group.py:16 users/models/user.py:712
#: 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/backup.py:31 assets/models/cmd_filter.py:42
#: 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
#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21
#: 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 "コンマ区切り文字列の形式。* はすべて一致することを示します。"
#: 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
#: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260
@@ -186,7 +186,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_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"
msgstr "IP"
@@ -269,7 +269,7 @@ msgid "Application"
msgstr "アプリケーション"
#: 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
#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22
#: terminal/backends/command/serializers.py:36 terminal/models/session.py:49
@@ -280,7 +280,7 @@ msgid "System user"
msgstr "システムユーザー"
#: 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"
msgstr "バージョン"
@@ -310,7 +310,7 @@ msgstr "カテゴリ"
#: assets/models/cmd_filter.py:86 assets/models/user.py:251
#: authentication/models.py:70 perms/models/application_permission.py:24
#: 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/ticket/apply_application.py:18
#: tickets/models/ticket/general.py:273
@@ -353,7 +353,7 @@ msgid "Type display"
msgstr "タイプ表示"
#: 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/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/account.py:18
@@ -367,7 +367,7 @@ msgstr "タイプ表示"
msgid "Date created"
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/serializers/account.py:21 assets/serializers/cmd_filter.py:29
#: assets/serializers/cmd_filter.py:49 common/db/models.py:117
@@ -445,7 +445,7 @@ msgid "Application path"
msgstr "アプリケーションパス"
#: 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/common.py:59
#: xpack/plugins/change_auth_plan/serializers/asset.py:67
@@ -634,7 +634,7 @@ msgid "Is active"
msgstr "アクティブです。"
#: 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"
msgstr "管理ユーザー"
@@ -650,7 +650,7 @@ msgstr "資産番号"
msgid "Labels"
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/cmd_filter.py:103 assets/models/group.py:21
#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71
@@ -786,32 +786,32 @@ msgstr "成功は"
msgid "Account backup execution"
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/utils.py:84
msgid "Unknown"
msgstr "不明"
#: assets/models/base.py:31
#: assets/models/base.py:30
msgid "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/asset.py:199
#: xpack/plugins/cloud/const.py:41
msgid "Failed"
msgstr "失敗しました"
#: assets/models/base.py:38 assets/serializers/domain.py:47
#: assets/models/base.py:37 assets/serializers/domain.py:47
msgid "Connectivity"
msgstr "接続性"
#: assets/models/base.py:40 authentication/models.py:263
#: assets/models/base.py:39 authentication/models.py:263
msgid "Date verified"
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
#: audits/signal_handlers.py:58 authentication/confirm/password.py:9
#: authentication/forms.py:32
@@ -829,14 +829,14 @@ msgstr "確認済みの日付"
msgid "Password"
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:130
#: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key"
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:202
msgid "SSH public key"
@@ -889,7 +889,7 @@ msgstr "デフォルトクラスター"
msgid "User group"
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"
msgstr "コマンドフィルター"
@@ -954,7 +954,7 @@ msgstr "テストゲートウェイ"
msgid "Unable to connect to port {port} on {ip}"
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
msgid "Authentication failed"
msgstr "認証に失敗しました"
@@ -1015,7 +1015,7 @@ msgstr "フルバリュー"
msgid "Parent key"
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
msgid "Node"
msgstr "ノード"
@@ -1095,7 +1095,7 @@ msgstr "ユーザースイッチ"
msgid "Switch from"
msgstr "から切り替え"
#: assets/models/user.py:345
#: assets/models/user.py:356
msgid "Can match system user"
msgstr "システムユーザーに一致できます"
@@ -1192,7 +1192,7 @@ msgstr "ssh秘密鍵"
msgid "Key password"
msgstr "キーパスワード"
#: assets/serializers/base.py:58
#: assets/serializers/base.py:58 assets/serializers/utils.py:24
msgid "private key invalid or passphrase error"
msgstr "秘密鍵が無効またはpassphraseエラー"
@@ -1205,7 +1205,7 @@ msgid "Pattern"
msgstr "パターン"
#: 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
msgid "Assets amount"
msgstr "資産額"
@@ -1234,86 +1234,81 @@ msgstr "同じレベルのノード名を同じにすることはできません
msgid "SSH key fingerprint"
msgstr "SSHキー指紋"
#: assets/serializers/system_user.py:40
#: perms/serializers/application/permission.py:46
msgid "Apps amount"
msgstr "アプリの量"
#: assets/serializers/system_user.py:62
#: assets/serializers/system_user.py:59
#: perms/serializers/asset/permission.py:50
msgid "Nodes amount"
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"
msgstr "ログインモード表示"
#: assets/serializers/system_user.py:66
#: assets/serializers/system_user.py:63
msgid "Ad domain"
msgstr "広告ドメイン"
#: assets/serializers/system_user.py:67
#: assets/serializers/system_user.py:64
msgid "Is asset protocol"
msgstr "資産プロトコルです"
#: assets/serializers/system_user.py:68
#: assets/serializers/system_user.py:65
msgid "Only ssh and automatic login system users are supported"
msgstr "sshと自動ログインシステムのユーザーのみがサポートされています"
#: assets/serializers/system_user.py:108
#: assets/serializers/system_user.py:105
msgid "Username same with user with protocol {} only allow 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"
msgstr "特別なcharは許可されていません"
#: assets/serializers/system_user.py:131
#: assets/serializers/system_user.py:128
msgid "* Automatic login mode must fill in the username."
msgstr "* 自動ログインモードはユーザー名を入力する必要があります。"
#: assets/serializers/system_user.py:146
#: assets/serializers/system_user.py:143
msgid "Path should starts with /"
msgstr "パスは/で始まる必要があります"
#: assets/serializers/system_user.py:158
#: assets/serializers/system_user.py:155
msgid "Password or private key required"
msgstr "パスワードまたは秘密鍵が必要"
#: assets/serializers/system_user.py:172
#: assets/serializers/system_user.py:169
msgid "Only ssh protocol system users are allowed"
msgstr "Sshプロトコルシステムユーザーのみが許可されています"
#: assets/serializers/system_user.py:176
#: assets/serializers/system_user.py:173
msgid "The protocol must be consistent with the current user: {}"
msgstr "プロトコルは現在のユーザーと一致している必要があります: {}"
#: assets/serializers/system_user.py:180
#: assets/serializers/system_user.py:177
msgid "Only system users with automatic login are allowed"
msgstr "自動ログインを持つシステムユーザーのみが許可されます"
#: assets/serializers/system_user.py:288
#: assets/serializers/system_user.py:284
msgid "System user name"
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
msgid "Org name"
msgstr "組織名"
#: assets/serializers/system_user.py:298
#: assets/serializers/system_user.py:294
msgid "Asset hostname"
msgstr "資産ホスト名"
#: assets/serializers/utils.py:11
#: assets/serializers/utils.py:13
msgid "Password can not contains `{{` "
msgstr "パスワードには '{{' を含まない"
#: assets/serializers/utils.py:14
#: assets/serializers/utils.py:16
msgid "Password can not contains `'` "
msgstr "パスワードには `'` を含まない"
#: assets/serializers/utils.py:16
#: assets/serializers/utils.py:18
msgid "Password can not contains `\"` "
msgstr "パスワードには `\"` を含まない"
@@ -1429,6 +1424,10 @@ msgid "For security, do not push user {}"
msgstr "セキュリティのために、ユーザー {} をプッシュしないでください"
#: 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"
msgstr "一致する資産がない、タスクを停止"
@@ -1664,7 +1663,8 @@ msgstr "企業微信"
#: audits/signal_handlers.py:62 authentication/views/feishu.py:144
#: 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"
msgstr "本を飛ばす"
@@ -1722,7 +1722,7 @@ msgid "Authentication"
msgstr "認証"
#: 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"
msgstr "ユーザーが無効、無効、または期限切れです"
@@ -1937,15 +1937,15 @@ msgstr "本を飛ばすは拘束されていません"
msgid "Your password is invalid"
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"
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"
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"
msgstr ""
"パスワードの有効期限が切れました。ログインする前にリセットしてください。"
@@ -2044,15 +2044,15 @@ msgstr "電話番号を設定して有効にする"
msgid "Clear phone number to disable"
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): {}"
msgstr "認証に失敗しました (ログインチェックが失敗する前): {}"
#: authentication/mixins.py:256
#: authentication/mixins.py:257
msgid "The MFA type ({}) is not enabled"
msgstr "MFAタイプ ({}) が有効になっていない"
#: authentication/mixins.py:296
#: authentication/mixins.py:297
msgid "Please change your password"
msgstr "パスワードを変更してください"
@@ -2263,7 +2263,7 @@ msgstr "コードエラー"
#: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_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_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@@ -2747,11 +2747,11 @@ msgstr "特殊文字を含むべきではない"
msgid "The mobile phone number format is incorrect"
msgstr "携帯電話番号の形式が正しくありません"
#: jumpserver/conf.py:412
#: jumpserver/conf.py:416
msgid "Create account successfully"
msgstr "アカウントを正常に作成"
#: jumpserver/conf.py:414
#: jumpserver/conf.py:418
msgid "Your account has been created successfully"
msgstr "アカウントが正常に作成されました"
@@ -3198,6 +3198,10 @@ msgstr "ユーザーグループの量"
msgid "System users amount"
msgstr "システムユーザー数"
#: perms/serializers/application/permission.py:46
msgid "Apps amount"
msgstr "アプリの量"
#: perms/serializers/application/permission.py:79
msgid ""
"The application list contains applications that are different from the "
@@ -3603,7 +3607,7 @@ msgstr "CAS"
msgid "Enable CAS Auth"
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"
msgstr "サービス側アドレス"
@@ -3636,7 +3640,7 @@ msgstr "そうでない場合はユーザーを作成"
msgid "Enable DingTalk Auth"
msgstr "ピン認証の有効化"
#: settings/serializers/auth/feishu.py:14
#: settings/serializers/auth/feishu.py:16
msgid "Enable FeiShu Auth"
msgstr "飛本認証の有効化"
@@ -3723,11 +3727,11 @@ msgstr "クライアントID"
msgid "Client Secret"
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"
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"
msgstr "プロバイダートークンエンドポイント"
@@ -3735,15 +3739,15 @@ msgstr "プロバイダートークンエンドポイント"
msgid "Client authentication method"
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"
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"
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
msgid "Always update user"
msgstr "常にユーザーを更新"
@@ -3772,51 +3776,59 @@ msgstr ""
"ユーザー属性マッピングは、OpenIDのユーザー属性を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"
msgstr "Keycloakを使用する"
#: settings/serializers/auth/oidc.py:52
#: settings/serializers/auth/oidc.py:57
msgid "Realm name"
msgstr "レルム名"
#: settings/serializers/auth/oidc.py:58
#: settings/serializers/auth/oidc.py:63
msgid "Enable OPENID Auth"
msgstr "OIDC認証の有効化"
#: settings/serializers/auth/oidc.py:60
#: settings/serializers/auth/oidc.py:65
msgid "Provider endpoint"
msgstr "プロバイダーエンドポイント"
#: settings/serializers/auth/oidc.py:69
#: settings/serializers/auth/oidc.py:74
msgid "Provider jwks endpoint"
msgstr "プロバイダーjwksエンドポイント"
#: settings/serializers/auth/oidc.py:78
#: settings/serializers/auth/oidc.py:83
msgid "Provider sign alg"
msgstr "プロビダーサインalg"
#: settings/serializers/auth/oidc.py:81
#: settings/serializers/auth/oidc.py:86
msgid "Provider sign key"
msgstr "プロバイダ署名キー"
#: settings/serializers/auth/oidc.py:83
#: settings/serializers/auth/oidc.py:88
msgid "Scopes"
msgstr "スコープ"
#: settings/serializers/auth/oidc.py:85
#: settings/serializers/auth/oidc.py:90
msgid "Id token max age"
msgstr "IDトークンの最大年齢"
#: settings/serializers/auth/oidc.py:88
#: settings/serializers/auth/oidc.py:93
msgid "Id token include claims"
msgstr "IDトークンにはクレームが含まれます"
#: settings/serializers/auth/oidc.py:90
#: settings/serializers/auth/oidc.py:95
msgid "Use state"
msgstr "使用状態"
#: settings/serializers/auth/oidc.py:91
#: settings/serializers/auth/oidc.py:96
msgid "Use nonce"
msgstr "Nonceを使用"
@@ -4731,8 +4743,8 @@ msgstr "期限切れです。"
#, python-format
msgid ""
"\n"
" Your password has expired, please click <a href="
"\"%(user_password_update_url)s\"> this link </a> update password.\n"
" Your password has expired, please click <a "
"href=\"%(user_password_update_url)s\"> this link </a> update password.\n"
" "
msgstr ""
"\n"
@@ -4753,34 +4765,34 @@ msgid ""
" "
msgstr ""
"\n"
" クリックしてください <a href=\"%(user_password_update_url)s"
"\"> リンク </a> パスワードの更新\n"
" クリックしてください <a "
"href=\"%(user_password_update_url)s\"> リンク </a> パスワードの更新\n"
" "
#: templates/_message.html:43
#, python-format
msgid ""
"\n"
" Your information was incomplete. Please click <a href="
"\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" Your information was incomplete. Please click <a "
"href=\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" "
msgstr ""
"\n"
" あなたの情報が不完全なので、クリックしてください。 <a href="
"\"%(first_login_url)s\"> リンク </a> 補完\n"
" あなたの情報が不完全なので、クリックしてください。 <a "
"href=\"%(first_login_url)s\"> リンク </a> 補完\n"
" "
#: templates/_message.html:56
#, python-format
msgid ""
"\n"
" Your ssh public key not set or expired. Please click <a href="
"\"%(user_pubkey_update)s\"> this link </a>to update\n"
" Your ssh public key not set or expired. Please click <a "
"href=\"%(user_pubkey_update)s\"> this link </a>to update\n"
" "
msgstr ""
"\n"
" SSHキーが設定されていないか無効になっている場合は、 <a href="
"\"%(user_pubkey_update)s\"> リンク </a> 更新\n"
" SSHキーが設定されていないか無効になっている場合は、 <a "
"href=\"%(user_pubkey_update)s\"> リンク </a> 更新\n"
" "
#: templates/_mfa_login_field.html:28
@@ -5000,7 +5012,7 @@ msgid "RDP Port"
msgstr "RDP ポート"
#: 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:90 terminal/serializers/storage.py:98
msgid "Endpoint"
@@ -5038,23 +5050,23 @@ msgstr "リプレイ"
msgid "Date end"
msgstr "終了日"
#: terminal/models/session.py:261
#: terminal/models/session.py:265
msgid "Session record"
msgstr "セッション記録"
#: terminal/models/session.py:263
#: terminal/models/session.py:267
msgid "Can monitor session"
msgstr "セッションを監視できます"
#: terminal/models/session.py:264
#: terminal/models/session.py:268
msgid "Can share session"
msgstr "セッションを共有できます"
#: terminal/models/session.py:265
#: terminal/models/session.py:269
msgid "Can terminate session"
msgstr "セッションを終了できます"
#: terminal/models/session.py:266
#: terminal/models/session.py:270
msgid "Can validate session action perm"
msgstr "セッションアクションのパーマを検証できます"
@@ -5139,15 +5151,15 @@ msgstr "スレッド"
msgid "Boot Time"
msgstr "ブート時間"
#: terminal/models/storage.py:29
#: terminal/models/storage.py:28
msgid "Default storage"
msgstr "デフォルトのストレージ"
#: terminal/models/storage.py:139 terminal/models/terminal.py:109
#: terminal/models/storage.py:141 terminal/models/terminal.py:109
msgid "Command storage"
msgstr "コマンドストレージ"
#: terminal/models/storage.py:199 terminal/models/terminal.py:110
#: terminal/models/storage.py:201 terminal/models/terminal.py:110
msgid "Replay storage"
msgstr "再生ストレージ"
@@ -5191,15 +5203,15 @@ msgstr "レベル"
msgid "Batch danger command alert"
msgstr "一括危険コマンド警告"
#: terminal/serializers/endpoint.py:14
#: terminal/serializers/endpoint.py:15
msgid "Magnus listen db port"
msgstr "Magnus がリッスンするデータベース ポート"
#: terminal/serializers/endpoint.py:17
#: terminal/serializers/endpoint.py:18
msgid "Magnus Listen port range"
msgstr "Magnus がリッスンするポート範囲"
#: terminal/serializers/endpoint.py:19
#: terminal/serializers/endpoint.py:20
msgid ""
"The range of ports that Magnus listens on is modified in the configuration "
"file"
@@ -5207,10 +5219,15 @@ msgstr "Magnus がリッスンするポート範囲を構成ファイルで変
#: terminal/serializers/endpoint.py:51
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 ""
"異なるエンドポイントの下に競合するアセット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
msgid "Terminal display"
@@ -5334,7 +5351,7 @@ msgstr "見つかりません"
msgid "view"
msgstr "表示"
#: terminal/utils/db_port_mapper.py:65
#: terminal/utils/db_port_mapper.py:78
msgid ""
"No available port is matched. The number of databases may have exceeded 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 ""
"No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file."
@@ -5352,7 +5369,7 @@ 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: {}"
msgstr "使用可能なすべてのポート数: {}、すでに使用しているポート数: {}"
@@ -5444,19 +5461,19 @@ msgstr ""
"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: "
"{}"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "Change field"
msgstr "フィールドを変更"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "Before change"
msgstr "変更前"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "After change"
msgstr "変更後"
#: tickets/handlers/base.py:96
#: tickets/handlers/base.py:98
msgid "{} {} the ticket"
msgstr "{} {} チケット"
@@ -7071,3 +7088,9 @@ msgstr "究極のエディション"
#: xpack/plugins/license/models.py:77
msgid "Community edition"
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
oid sha256:b143c62843946c3e18b623d05065f12e9d3c578efe5cd0d2016056d2b8448ae8
size 109495
oid sha256:62e47d2577f103b524a1ae75e3357cdeb5f90d89f89c58a8c8fa8f63d67749f8
size 110097

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\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"
"Last-Translator: ibuler <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
#: 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/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
#: settings/models.py:33 settings/serializers/sms.py:6
#: 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
#: users/models/group.py:15 users/models/user.py:673
#: xpack/plugins/cloud/models.py:27
@@ -54,14 +54,14 @@ msgstr "激活中"
#: acls/models/base.py:32 applications/models/application.py:232
#: 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/cmd_filter.py:100 assets/models/domain.py:24
#: assets/models/domain.py:65 assets/models/group.py:23
#: 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
#: 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
#: users/models/group.py:16 users/models/user.py:712
#: 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/backup.py:31 assets/models/cmd_filter.py:42
#: 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
#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21
#: 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 "格式为逗号分隔的字符串, * 表示匹配所有. "
#: 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
#: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260
@@ -184,7 +184,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_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"
msgstr "IP"
@@ -264,7 +264,7 @@ msgid "Application"
msgstr "应用程序"
#: 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
#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22
#: terminal/backends/command/serializers.py:36 terminal/models/session.py:49
@@ -275,7 +275,7 @@ msgid "System user"
msgstr "系统用户"
#: 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"
msgstr "版本"
@@ -305,7 +305,7 @@ msgstr "类别"
#: assets/models/cmd_filter.py:86 assets/models/user.py:251
#: authentication/models.py:70 perms/models/application_permission.py:24
#: 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/ticket/apply_application.py:18
#: tickets/models/ticket/general.py:273
@@ -348,7 +348,7 @@ msgid "Type display"
msgstr "类型名称"
#: 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/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/account.py:18
@@ -362,7 +362,7 @@ msgstr "类型名称"
msgid "Date created"
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/serializers/account.py:21 assets/serializers/cmd_filter.py:29
#: assets/serializers/cmd_filter.py:49 common/db/models.py:117
@@ -440,7 +440,7 @@ msgid "Application path"
msgstr "应用路径"
#: 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/common.py:59
#: xpack/plugins/change_auth_plan/serializers/asset.py:67
@@ -627,7 +627,7 @@ msgid "Is active"
msgstr "激活"
#: 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"
msgstr "特权用户"
@@ -643,7 +643,7 @@ msgstr "资产编号"
msgid "Labels"
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/cmd_filter.py:103 assets/models/group.py:21
#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71
@@ -779,32 +779,32 @@ msgstr "是否成功"
msgid "Account backup execution"
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/utils.py:84
msgid "Unknown"
msgstr "未知"
#: assets/models/base.py:31
#: assets/models/base.py:30
msgid "Ok"
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/asset.py:199
#: xpack/plugins/cloud/const.py:41
msgid "Failed"
msgstr "失败"
#: assets/models/base.py:38 assets/serializers/domain.py:47
#: assets/models/base.py:37 assets/serializers/domain.py:47
msgid "Connectivity"
msgstr "可连接性"
#: assets/models/base.py:40 authentication/models.py:263
#: assets/models/base.py:39 authentication/models.py:263
msgid "Date verified"
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
#: audits/signal_handlers.py:58 authentication/confirm/password.py:9
#: authentication/forms.py:32
@@ -822,14 +822,14 @@ msgstr "校验日期"
msgid "Password"
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:130
#: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key"
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:202
msgid "SSH public key"
@@ -882,7 +882,7 @@ msgstr "默认Cluster"
msgid "User group"
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"
msgstr "命令过滤器"
@@ -947,7 +947,7 @@ msgstr "测试网关"
msgid "Unable to connect to port {port} on {ip}"
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
msgid "Authentication failed"
msgstr "认证失败"
@@ -1008,7 +1008,7 @@ msgstr "全称"
msgid "Parent key"
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
msgid "Node"
msgstr "节点"
@@ -1088,7 +1088,7 @@ msgstr "用户切换"
msgid "Switch from"
msgstr "切换自"
#: assets/models/user.py:345
#: assets/models/user.py:356
msgid "Can match system user"
msgstr "可以匹配系统用户"
@@ -1182,7 +1182,7 @@ msgstr "ssh私钥"
msgid "Key password"
msgstr "密钥密码"
#: assets/serializers/base.py:58
#: assets/serializers/base.py:58 assets/serializers/utils.py:24
msgid "private key invalid or passphrase error"
msgstr "密钥不合法或密钥密码错误"
@@ -1195,7 +1195,7 @@ msgid "Pattern"
msgstr "模式"
#: 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
msgid "Assets amount"
msgstr "资产数量"
@@ -1224,86 +1224,81 @@ msgstr "同级别节点名字不能重复"
msgid "SSH key fingerprint"
msgstr "密钥指纹"
#: assets/serializers/system_user.py:40
#: perms/serializers/application/permission.py:46
msgid "Apps amount"
msgstr "应用数量"
#: assets/serializers/system_user.py:62
#: assets/serializers/system_user.py:59
#: perms/serializers/asset/permission.py:50
msgid "Nodes amount"
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"
msgstr "认证方式名称"
#: assets/serializers/system_user.py:66
#: assets/serializers/system_user.py:63
msgid "Ad domain"
msgstr "Ad 网域"
#: assets/serializers/system_user.py:67
#: assets/serializers/system_user.py:64
msgid "Is asset protocol"
msgstr "资产协议"
#: assets/serializers/system_user.py:68
#: assets/serializers/system_user.py:65
msgid "Only ssh and automatic login system users are supported"
msgstr "仅支持ssh协议和自动登录的系统用户"
#: assets/serializers/system_user.py:108
#: assets/serializers/system_user.py:105
msgid "Username same with user with protocol {} only allow 1"
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"
msgstr "不能包含特殊字符"
#: assets/serializers/system_user.py:131
#: assets/serializers/system_user.py:128
msgid "* Automatic login mode must fill in the username."
msgstr "自动登录模式,必须填写用户名"
#: assets/serializers/system_user.py:146
#: assets/serializers/system_user.py:143
msgid "Path should starts with /"
msgstr "路径应该以 / 开头"
#: assets/serializers/system_user.py:158
#: assets/serializers/system_user.py:155
msgid "Password or private key required"
msgstr "密码或密钥密码需要一个"
#: assets/serializers/system_user.py:172
#: assets/serializers/system_user.py:169
msgid "Only ssh protocol system users are allowed"
msgstr "仅允许ssh协议的系统用户"
#: assets/serializers/system_user.py:176
#: assets/serializers/system_user.py:173
msgid "The protocol must be consistent with the current user: {}"
msgstr "协议必须和当前用户保持一致: {}"
#: assets/serializers/system_user.py:180
#: assets/serializers/system_user.py:177
msgid "Only system users with automatic login are allowed"
msgstr "仅允许自动登录的系统用户"
#: assets/serializers/system_user.py:288
#: assets/serializers/system_user.py:284
msgid "System user name"
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
msgid "Org name"
msgstr "组织名称"
#: assets/serializers/system_user.py:298
#: assets/serializers/system_user.py:294
msgid "Asset hostname"
msgstr "资产主机名"
#: assets/serializers/utils.py:11
#: assets/serializers/utils.py:13
msgid "Password can not contains `{{` "
msgstr "密码不能包含 `{{` 字符"
#: assets/serializers/utils.py:14
#: assets/serializers/utils.py:16
msgid "Password can not contains `'` "
msgstr "密码不能包含 `'` 字符"
#: assets/serializers/utils.py:16
#: assets/serializers/utils.py:18
msgid "Password can not contains `\"` "
msgstr "密码不能包含 `\"` 字符"
@@ -1415,6 +1410,10 @@ msgid "For security, do not push user {}"
msgstr "为了安全,禁止推送用户 {}"
#: 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"
msgstr "没有匹配到资产,结束任务"
@@ -1650,7 +1649,8 @@ msgstr "企业微信"
#: audits/signal_handlers.py:62 authentication/views/feishu.py:144
#: 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"
msgstr "飞书"
@@ -1706,7 +1706,7 @@ msgid "Authentication"
msgstr "认证"
#: 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"
msgstr "用户无效,已禁用或已过期"
@@ -1913,15 +1913,15 @@ msgstr "没有绑定飞书"
msgid "Your password is invalid"
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"
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"
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"
msgstr "您的密码已过期,先修改再登录"
@@ -2018,15 +2018,15 @@ msgstr "设置手机号码启用"
msgid "Clear phone number to disable"
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): {}"
msgstr "认证失败(登录前检查失败): {}"
#: authentication/mixins.py:256
#: authentication/mixins.py:257
msgid "The MFA type ({}) is not enabled"
msgstr "该 MFA ({}) 方式没有启用"
#: authentication/mixins.py:296
#: authentication/mixins.py:297
msgid "Please change your password"
msgstr "请修改密码"
@@ -2233,7 +2233,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_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_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@@ -2708,11 +2708,11 @@ msgstr "不能包含特殊字符"
msgid "The mobile phone number format is incorrect"
msgstr "手机号格式不正确"
#: jumpserver/conf.py:412
#: jumpserver/conf.py:416
msgid "Create account successfully"
msgstr "创建账号成功"
#: jumpserver/conf.py:414
#: jumpserver/conf.py:418
msgid "Your account has been created successfully"
msgstr "你的账号已创建成功"
@@ -3153,6 +3153,10 @@ msgstr "用户组数量"
msgid "System users amount"
msgstr "系统用户数量"
#: perms/serializers/application/permission.py:46
msgid "Apps amount"
msgstr "应用数量"
#: perms/serializers/application/permission.py:79
msgid ""
"The application list contains applications that are different from the "
@@ -3555,7 +3559,7 @@ msgstr "CAS"
msgid "Enable CAS Auth"
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"
msgstr "服务端地址"
@@ -3588,7 +3592,7 @@ msgstr "创建用户(如果不存在)"
msgid "Enable DingTalk Auth"
msgstr "启用钉钉认证"
#: settings/serializers/auth/feishu.py:14
#: settings/serializers/auth/feishu.py:16
msgid "Enable FeiShu Auth"
msgstr "启用飞书认证"
@@ -3675,11 +3679,11 @@ msgstr "客户端 ID"
msgid "Client Secret"
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"
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"
msgstr "token 端点地址"
@@ -3687,15 +3691,15 @@ msgstr "token 端点地址"
msgid "Client authentication method"
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"
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"
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
msgid "Always update user"
msgstr "总是更新用户信息"
@@ -3724,51 +3728,59 @@ msgstr ""
"用户属性映射代表怎样将OpenID中用户属性映射到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"
msgstr "使用 Keycloak"
#: settings/serializers/auth/oidc.py:52
#: settings/serializers/auth/oidc.py:57
msgid "Realm name"
msgstr "域"
#: settings/serializers/auth/oidc.py:58
#: settings/serializers/auth/oidc.py:63
msgid "Enable OPENID Auth"
msgstr "启用 OIDC 认证"
#: settings/serializers/auth/oidc.py:60
#: settings/serializers/auth/oidc.py:65
msgid "Provider endpoint"
msgstr "端点地址"
#: settings/serializers/auth/oidc.py:69
#: settings/serializers/auth/oidc.py:74
msgid "Provider jwks endpoint"
msgstr "jwks 端点地址"
#: settings/serializers/auth/oidc.py:78
#: settings/serializers/auth/oidc.py:83
msgid "Provider sign alg"
msgstr "签名算法"
#: settings/serializers/auth/oidc.py:81
#: settings/serializers/auth/oidc.py:86
msgid "Provider sign key"
msgstr "签名 Key"
#: settings/serializers/auth/oidc.py:83
#: settings/serializers/auth/oidc.py:88
msgid "Scopes"
msgstr "连接范围"
#: settings/serializers/auth/oidc.py:85
#: settings/serializers/auth/oidc.py:90
msgid "Id token max age"
msgstr "令牌有效时间"
#: settings/serializers/auth/oidc.py:88
#: settings/serializers/auth/oidc.py:93
msgid "Id token include claims"
msgstr "声明"
#: settings/serializers/auth/oidc.py:90
#: settings/serializers/auth/oidc.py:95
msgid "Use state"
msgstr "使用状态"
#: settings/serializers/auth/oidc.py:91
#: settings/serializers/auth/oidc.py:96
msgid "Use nonce"
msgstr "临时使用"
@@ -4652,13 +4664,13 @@ msgstr "过期。"
#, python-format
msgid ""
"\n"
" Your password has expired, please click <a href="
"\"%(user_password_update_url)s\"> this link </a> update password.\n"
" Your password has expired, please click <a "
"href=\"%(user_password_update_url)s\"> this link </a> update password.\n"
" "
msgstr ""
"\n"
" 您的密码已经过期,请点击 <a href="
"\"%(user_password_update_url)s\"> 链接 </a> 更新密码\n"
" 您的密码已经过期,请点击 <a "
"href=\"%(user_password_update_url)s\"> 链接 </a> 更新密码\n"
" "
#: templates/_message.html:30
@@ -4682,8 +4694,8 @@ msgstr ""
#, python-format
msgid ""
"\n"
" Your information was incomplete. Please click <a href="
"\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" Your information was incomplete. Please click <a "
"href=\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" "
msgstr ""
"\n"
@@ -4695,13 +4707,13 @@ msgstr ""
#, python-format
msgid ""
"\n"
" Your ssh public key not set or expired. Please click <a href="
"\"%(user_pubkey_update)s\"> this link </a>to update\n"
" Your ssh public key not set or expired. Please click <a "
"href=\"%(user_pubkey_update)s\"> this link </a>to update\n"
" "
msgstr ""
"\n"
" 您的SSH密钥没有设置或已失效请点击 <a href="
"\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" 您的SSH密钥没有设置或已失效请点击 <a "
"href=\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" "
#: templates/_mfa_login_field.html:28
@@ -4914,7 +4926,7 @@ msgid "RDP Port"
msgstr "RDP 端口"
#: 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:90 terminal/serializers/storage.py:98
msgid "Endpoint"
@@ -4952,23 +4964,23 @@ msgstr "回放"
msgid "Date end"
msgstr "结束日期"
#: terminal/models/session.py:261
#: terminal/models/session.py:265
msgid "Session record"
msgstr "会话记录"
#: terminal/models/session.py:263
#: terminal/models/session.py:267
msgid "Can monitor session"
msgstr "可以监控会话"
#: terminal/models/session.py:264
#: terminal/models/session.py:268
msgid "Can share session"
msgstr "可以分享会话"
#: terminal/models/session.py:265
#: terminal/models/session.py:269
msgid "Can terminate session"
msgstr "可以终断会话"
#: terminal/models/session.py:266
#: terminal/models/session.py:270
msgid "Can validate session action perm"
msgstr "可以验证会话动作权限"
@@ -5053,15 +5065,15 @@ msgstr "线程数"
msgid "Boot Time"
msgstr "运行时间"
#: terminal/models/storage.py:29
#: terminal/models/storage.py:28
msgid "Default storage"
msgstr "默认存储"
#: terminal/models/storage.py:139 terminal/models/terminal.py:109
#: terminal/models/storage.py:141 terminal/models/terminal.py:109
msgid "Command storage"
msgstr "命令存储"
#: terminal/models/storage.py:199 terminal/models/terminal.py:110
#: terminal/models/storage.py:201 terminal/models/terminal.py:110
msgid "Replay storage"
msgstr "录像存储"
@@ -5105,15 +5117,15 @@ msgstr "级别"
msgid "Batch danger command alert"
msgstr "批量危险命令告警"
#: terminal/serializers/endpoint.py:14
#: terminal/serializers/endpoint.py:15
msgid "Magnus listen db port"
msgstr "Magnus 监听的数据库端口"
#: terminal/serializers/endpoint.py:17
#: terminal/serializers/endpoint.py:18
msgid "Magnus Listen port range"
msgstr "Magnus 监听的端口范围"
#: terminal/serializers/endpoint.py:19
#: terminal/serializers/endpoint.py:20
msgid ""
"The range of ports that Magnus listens on is modified in the configuration "
"file"
@@ -5121,8 +5133,14 @@ msgstr "请在配置文件中修改 Magnus 监听的端口范围"
#: terminal/serializers/endpoint.py:51
msgid ""
"If asset IP addresses under different endpoints conflict, use asset labels"
msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现"
"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 ""
"格式为逗号分隔的字符串, * 表示匹配所有。例如: 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
msgid "Terminal display"
@@ -5246,7 +5264,7 @@ msgstr "没有发现"
msgid "view"
msgstr "查看"
#: terminal/utils/db_port_mapper.py:65
#: terminal/utils/db_port_mapper.py:78
msgid ""
"No available port is matched. The number of databases may have exceeded 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 ""
"No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file."
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: {}"
msgstr "所有可用端口数量:{},已使用端口数量:{}"
@@ -5351,19 +5369,19 @@ msgid ""
msgstr ""
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "Change field"
msgstr "变更字段"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "Before change"
msgstr "变更前"
#: tickets/handlers/base.py:84
#: tickets/handlers/base.py:86
msgid "After change"
msgstr "变更后"
#: tickets/handlers/base.py:96
#: tickets/handlers/base.py:98
msgid "{} {} the ticket"
msgstr "{} {} 工单"
@@ -6961,3 +6979,7 @@ msgstr "旗舰版"
#: xpack/plugins/license/models.py:77
msgid "Community edition"
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):
pk = self.kwargs.get('pk')
return AsyncResult(pk)
return AsyncResult(str(pk))
class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):

View File

@@ -2,9 +2,9 @@
#
from django.conf import settings
from .ansible.inventory import BaseInventory
from common.utils import get_logger
from .ansible.inventory import BaseInventory
__all__ = [
'JMSInventory', 'JMSCustomInventory',
@@ -110,7 +110,10 @@ class JMSInventory(JMSBaseInventory):
if self.system_user:
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:
return {}

View File

@@ -9,7 +9,13 @@ __all__ = ['FeiShuSettingSerializer']
class FeiShuSettingSerializer(serializers.Serializer):
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_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 '
'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):

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
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 ..utils import db_port_manager
from ..models import Endpoint, EndpointRule
@@ -46,9 +47,12 @@ class EndpointSerializer(BulkModelSerializer):
class EndpointRuleSerializer(BulkModelSerializer):
_ip_group_help_text = '{} <br> {}'.format(
ip_group_help_text,
_('If asset IP addresses under different endpoints conflict, use asset labels')
_ip_group_help_text = _(
'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'
)
ip_group = serializers.ListField(
default=['*'], label=_('IP'), help_text=_ip_group_help_text,

View File

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

View File

@@ -35,9 +35,22 @@ class DBPortManager(object):
def magnus_listen_port_range(self):
return settings.MAGNUS_PORTS
def init(self):
@staticmethod
def fetch_dbs():
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]
mapper = dict(zip(self.all_available_ports, list(db_ids)))
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.utils.translation import ugettext as _
from common.utils import get_logger
from tickets.const import TicketState, TicketType
from tickets.utils import (
send_ticket_processed_mail_to_applicant,
send_ticket_applied_mail_to_assignees
)
from tickets.const import TicketState, TicketType
logger = get_logger(__name__)
@@ -96,11 +97,20 @@ class BaseHandler:
approve_info = _('{} {} the ticket').format(user_display, state_display)
context = self._diff_prev_approve_context(state)
context.update({'approve_info': approve_info})
body = self.safe_html_script(
render_to_string('tickets/ticket_approve_diff.html', context)
)
data = {
'body': render_to_string('tickets/ticket_approve_diff.html', context),
'body': body,
'user': user,
'user_display': str(user),
'type': 'state',
'state': state
}
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 %}
</tr>
{% endfor %}
</table>
</div>
{% endif %}

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ connection_params = {
if 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_certfile'] = settings.REDIS_SSL_CERT
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
sentinel_client = Sentinel(
**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,
'socket_timeout': REDIS_SENTINEL_SOCKET_TIMEOUT
}