Compare commits

...

24 Commits

Author SHA1 Message Date
fit2bot
1873702b25 feat: Update v2.28.7 2023-02-08 16:18:01 +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
27 changed files with 433 additions and 231 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 \ default-mysql-client \
iputils-ping \ iputils-ping \
locales \ locales \
netcat \ procps \
redis-server \ redis-tools \
telnet \ telnet \
vim \ vim \
unzip \ unzip \

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
dad45e7ace787a07db08830bbcda19af603a3888

View File

@@ -1,28 +1,27 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import io
import os import os
import uuid import uuid
from hashlib import md5 from hashlib import md5
import sshpubkeys import sshpubkeys
from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models
from django.db.models import QuerySet
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.db.models import QuerySet
from common.utils import random_string, signer from common.db import fields
from common.utils import random_string
from common.utils import ( from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty
) )
from common.utils.encode import ssh_pubkey_gen from common.utils.encode import (
from common.validators import alphanumeric parse_ssh_public_key_str, parse_ssh_private_key_str
from common.db import fields )
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
logger = get_logger(__file__) logger = get_logger(__file__)
@@ -64,16 +63,16 @@ class AuthMixin:
@property @property
def ssh_key_fingerprint(self): def ssh_key_fingerprint(self):
public_key = None
if self.public_key: if self.public_key:
public_key = self.public_key public_key = self.public_key
elif self.private_key: elif self.private_key:
try: try:
public_key = ssh_pubkey_gen(private_key=self.private_key, password=self.password) public_key = parse_ssh_public_key_str(self.private_key, password=self.password)
except IOError as e: except IOError as e:
return str(e) return str(e)
else: if not public_key:
return '' return ''
public_key_obj = sshpubkeys.SSHKey(public_key) public_key_obj = sshpubkeys.SSHKey(public_key)
fingerprint = public_key_obj.hash_md5() fingerprint = public_key_obj.hash_md5()
return fingerprint return fingerprint
@@ -88,24 +87,27 @@ class AuthMixin:
@property @property
def private_key_file(self): def private_key_file(self):
if not self.private_key_obj: if not self.private_key:
return None
private_key_str = parse_ssh_private_key_str(self.private_key,
password=self.password)
if not private_key_str:
return None return None
project_dir = settings.PROJECT_DIR project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp') tmp_dir = os.path.join(project_dir, 'tmp')
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest() key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name) key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path): if not os.path.exists(key_path):
self.private_key_obj.write_private_key_file(key_path) with open(key_path, 'w') as f:
f.write(private_key_str)
os.chmod(key_path, 0o400) os.chmod(key_path, 0o400)
return key_path return key_path
def get_private_key(self): def get_private_key(self):
if not self.private_key_obj: if not self.private_key:
return None return None
string_io = io.StringIO() return parse_ssh_private_key_str(self.private_key,
self.private_key_obj.write_private_key(string_io) password=self.password)
private_key = string_io.getvalue()
return private_key
@property @property
def public_key_obj(self): def public_key_obj(self):
@@ -234,4 +236,3 @@ class BaseUser(OrgModelMixin, AuthMixin):
class Meta: class Meta:
abstract = True abstract = True

View File

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

View File

@@ -1,16 +1,16 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count from django.db.models import Count
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.mixins.serializers import BulkSerializerMixin
from common.utils import ssh_pubkey_gen
from common.drf.fields import EncryptedField from common.drf.fields import EncryptedField
from common.drf.serializers import SecretReadableMixin from common.drf.serializers import SecretReadableMixin
from common.mixins.serializers import BulkSerializerMixin
from common.utils import parse_ssh_public_key_str
from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser, Asset
from .utils import validate_password_for_ansible
from .base import AuthSerializerMixin from .base import AuthSerializerMixin
from .utils import validate_password_for_ansible
from ..models import SystemUser, Asset
__all__ = [ __all__ = [
'SystemUserSerializer', 'MiniSystemUserSerializer', 'SystemUserSerializer', 'MiniSystemUserSerializer',
@@ -214,7 +214,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
elif attrs.get('private_key'): elif attrs.get('private_key'):
private_key = attrs['private_key'] private_key = attrs['private_key']
password = attrs.get('password') password = attrs.get('password')
public_key = ssh_pubkey_gen(private_key, password=password, username=username) public_key = parse_ssh_public_key_str(private_key, password=password)
attrs['public_key'] = public_key attrs['public_key'] = public_key
return attrs return attrs

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -78,6 +78,8 @@ AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION
AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION
AUTH_OPENID_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER AUTH_OPENID_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER
AUTH_OPENID_USER_ATTR_MAP = CONFIG.AUTH_OPENID_USER_ATTR_MAP AUTH_OPENID_USER_ATTR_MAP = CONFIG.AUTH_OPENID_USER_ATTR_MAP
AUTH_OPENID_PKCE = CONFIG.AUTH_OPENID_PKCE
AUTH_OPENID_CODE_CHALLENGE_METHOD = CONFIG.AUTH_OPENID_CODE_CHALLENGE_METHOD
AUTH_OPENID_AUTH_LOGIN_URL_NAME = 'authentication:openid:login' AUTH_OPENID_AUTH_LOGIN_URL_NAME = 'authentication:openid:login'
AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:openid:login-callback' AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:openid:login-callback'
AUTH_OPENID_AUTH_LOGOUT_URL_NAME = 'authentication:openid:logout' AUTH_OPENID_AUTH_LOGOUT_URL_NAME = 'authentication:openid:logout'

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-17 17:34+0800\n" "POT-Creation-Date: 2022-12-13 15:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -24,13 +24,13 @@ msgstr "Acls"
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
#: applications/models/application.py:219 assets/models/asset.py:138 #: applications/models/application.py:219 assets/models/asset.py:138
#: assets/models/base.py:175 assets/models/cluster.py:18 #: assets/models/base.py:173 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:27 assets/models/domain.py:23 #: assets/models/cmd_filter.py:27 assets/models/domain.py:23
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 #: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29
#: settings/models.py:33 settings/serializers/sms.py:6 #: settings/models.py:33 settings/serializers/sms.py:6
#: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87 #: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87
#: terminal/models/storage.py:27 terminal/models/task.py:16 #: terminal/models/storage.py:26 terminal/models/task.py:16
#: terminal/models/terminal.py:101 users/forms/profile.py:33 #: terminal/models/terminal.py:101 users/forms/profile.py:33
#: users/models/group.py:15 users/models/user.py:673 #: users/models/group.py:15 users/models/user.py:673
#: xpack/plugins/cloud/models.py:27 #: xpack/plugins/cloud/models.py:27
@@ -55,14 +55,14 @@ msgstr "アクティブ"
#: acls/models/base.py:32 applications/models/application.py:232 #: acls/models/base.py:32 applications/models/application.py:232
#: assets/models/asset.py:143 assets/models/asset.py:231 #: assets/models/asset.py:143 assets/models/asset.py:231
#: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/backup.py:54 assets/models/base.py:178
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:52 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:52
#: assets/models/cmd_filter.py:100 assets/models/domain.py:24 #: assets/models/cmd_filter.py:100 assets/models/domain.py:24
#: assets/models/domain.py:65 assets/models/group.py:23 #: assets/models/domain.py:65 assets/models/group.py:23
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38
#: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97 #: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97
#: terminal/models/storage.py:30 terminal/models/terminal.py:115 #: terminal/models/storage.py:29 terminal/models/terminal.py:115
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
#: users/models/group.py:16 users/models/user.py:712 #: users/models/group.py:16 users/models/user.py:712
#: xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/change_auth_plan/models/base.py:44
@@ -155,7 +155,7 @@ msgid "Format for comma-delimited string, with * indicating a match all. "
msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。" msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。"
#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:174
#: assets/models/gathered_user.py:15 audits/models.py:139 #: assets/models/gathered_user.py:15 audits/models.py:139
#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260 #: authentication/models.py:260
@@ -310,7 +310,7 @@ msgstr "カテゴリ"
#: assets/models/cmd_filter.py:86 assets/models/user.py:251 #: assets/models/cmd_filter.py:86 assets/models/user.py:251
#: authentication/models.py:70 perms/models/application_permission.py:24 #: authentication/models.py:70 perms/models/application_permission.py:24
#: perms/serializers/application/user_permission.py:34 #: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:59 terminal/models/storage.py:145 #: terminal/models/storage.py:58 terminal/models/storage.py:147
#: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/comment.py:26 tickets/models/flow.py:57
#: tickets/models/ticket/apply_application.py:18 #: tickets/models/ticket/apply_application.py:18
#: tickets/models/ticket/general.py:273 #: tickets/models/ticket/general.py:273
@@ -353,7 +353,7 @@ msgid "Type display"
msgstr "タイプ表示" msgstr "タイプ表示"
#: applications/serializers/application.py:105 assets/models/asset.py:230 #: applications/serializers/application.py:105 assets/models/asset.py:230
#: assets/models/base.py:181 assets/models/cluster.py:26 #: assets/models/base.py:179 assets/models/cluster.py:26
#: assets/models/cmd_filter.py:53 assets/models/domain.py:26 #: assets/models/cmd_filter.py:53 assets/models/domain.py:26
#: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/account.py:18 #: assets/models/label.py:25 assets/serializers/account.py:18
@@ -367,7 +367,7 @@ msgstr "タイプ表示"
msgid "Date created" msgid "Date created"
msgstr "作成された日付" msgstr "作成された日付"
#: applications/serializers/application.py:106 assets/models/base.py:182 #: applications/serializers/application.py:106 assets/models/base.py:180
#: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20 #: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20
#: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29 #: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29
#: assets/serializers/cmd_filter.py:49 common/db/models.py:117 #: assets/serializers/cmd_filter.py:49 common/db/models.py:117
@@ -650,7 +650,7 @@ msgstr "資産番号"
msgid "Labels" msgid "Labels"
msgstr "ラベル" msgstr "ラベル"
#: assets/models/asset.py:229 assets/models/base.py:183 #: assets/models/asset.py:229 assets/models/base.py:181
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:56 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:56
#: assets/models/cmd_filter.py:103 assets/models/group.py:21 #: assets/models/cmd_filter.py:103 assets/models/group.py:21
#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71 #: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71
@@ -786,32 +786,32 @@ msgstr "成功は"
msgid "Account backup execution" msgid "Account backup execution"
msgstr "アカウントバックアップの実行" msgstr "アカウントバックアップの実行"
#: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 #: assets/models/base.py:28 assets/tasks/const.py:51 audits/const.py:5
#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37
#: common/utils/ip/utils.py:84 #: common/utils/ip/utils.py:84
msgid "Unknown" msgid "Unknown"
msgstr "不明" msgstr "不明"
#: assets/models/base.py:31 #: assets/models/base.py:29
msgid "Ok" msgid "Ok"
msgstr "OK" msgstr "OK"
#: assets/models/base.py:32 audits/models.py:136 #: assets/models/base.py:30 audits/models.py:136
#: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/app.py:88
#: xpack/plugins/change_auth_plan/serializers/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:199
#: xpack/plugins/cloud/const.py:41 #: xpack/plugins/cloud/const.py:41
msgid "Failed" msgid "Failed"
msgstr "失敗しました" msgstr "失敗しました"
#: assets/models/base.py:38 assets/serializers/domain.py:47 #: assets/models/base.py:36 assets/serializers/domain.py:47
msgid "Connectivity" msgid "Connectivity"
msgstr "接続性" msgstr "接続性"
#: assets/models/base.py:40 authentication/models.py:263 #: assets/models/base.py:38 authentication/models.py:263
msgid "Date verified" msgid "Date verified"
msgstr "確認済みの日付" msgstr "確認済みの日付"
#: assets/models/base.py:177 assets/serializers/base.py:15 #: assets/models/base.py:175 assets/serializers/base.py:14
#: assets/serializers/base.py:37 assets/serializers/system_user.py:29 #: assets/serializers/base.py:37 assets/serializers/system_user.py:29
#: audits/signal_handlers.py:58 authentication/confirm/password.py:9 #: audits/signal_handlers.py:58 authentication/confirm/password.py:9
#: authentication/forms.py:32 #: authentication/forms.py:32
@@ -829,14 +829,14 @@ msgstr "確認済みの日付"
msgid "Password" msgid "Password"
msgstr "パスワード" msgstr "パスワード"
#: assets/models/base.py:178 assets/serializers/base.py:41 #: assets/models/base.py:176 assets/serializers/base.py:41
#: xpack/plugins/change_auth_plan/models/asset.py:53 #: xpack/plugins/change_auth_plan/models/asset.py:53
#: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:130
#: xpack/plugins/change_auth_plan/models/asset.py:206 #: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key" msgid "SSH private key"
msgstr "SSH秘密鍵" msgstr "SSH秘密鍵"
#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:56 #: assets/models/base.py:177 xpack/plugins/change_auth_plan/models/asset.py:56
#: xpack/plugins/change_auth_plan/models/asset.py:126 #: xpack/plugins/change_auth_plan/models/asset.py:126
#: xpack/plugins/change_auth_plan/models/asset.py:202 #: xpack/plugins/change_auth_plan/models/asset.py:202
msgid "SSH public key" msgid "SSH public key"
@@ -1192,7 +1192,7 @@ msgstr "ssh秘密鍵"
msgid "Key password" msgid "Key password"
msgstr "キーパスワード" msgstr "キーパスワード"
#: assets/serializers/base.py:58 #: assets/serializers/base.py:58 assets/serializers/utils.py:24
msgid "private key invalid or passphrase error" msgid "private key invalid or passphrase error"
msgstr "秘密鍵が無効またはpassphraseエラー" msgstr "秘密鍵が無効またはpassphraseエラー"
@@ -1305,15 +1305,15 @@ msgstr "組織名"
msgid "Asset hostname" msgid "Asset hostname"
msgstr "資産ホスト名" msgstr "資産ホスト名"
#: assets/serializers/utils.py:11 #: assets/serializers/utils.py:13
msgid "Password can not contains `{{` " msgid "Password can not contains `{{` "
msgstr "パスワードには '{{' を含まない" msgstr "パスワードには '{{' を含まない"
#: assets/serializers/utils.py:14 #: assets/serializers/utils.py:16
msgid "Password can not contains `'` " msgid "Password can not contains `'` "
msgstr "パスワードには `'` を含まない" msgstr "パスワードには `'` を含まない"
#: assets/serializers/utils.py:16 #: assets/serializers/utils.py:18
msgid "Password can not contains `\"` " msgid "Password can not contains `\"` "
msgstr "パスワードには `\"` を含まない" msgstr "パスワードには `\"` を含まない"
@@ -2263,7 +2263,7 @@ msgstr "コードエラー"
#: authentication/templates/authentication/_msg_reset_password_code.html:9 #: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:413 ops/tasks.py:145 ops/tasks.py:148 #: jumpserver/conf.py:416 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33 #: tickets/templates/tickets/approve_check_password.html:33
@@ -2747,11 +2747,11 @@ msgstr "特殊文字を含むべきではない"
msgid "The mobile phone number format is incorrect" msgid "The mobile phone number format is incorrect"
msgstr "携帯電話番号の形式が正しくありません" msgstr "携帯電話番号の形式が正しくありません"
#: jumpserver/conf.py:412 #: jumpserver/conf.py:415
msgid "Create account successfully" msgid "Create account successfully"
msgstr "アカウントを正常に作成" msgstr "アカウントを正常に作成"
#: jumpserver/conf.py:414 #: jumpserver/conf.py:417
msgid "Your account has been created successfully" msgid "Your account has been created successfully"
msgstr "アカウントが正常に作成されました" msgstr "アカウントが正常に作成されました"
@@ -3603,7 +3603,7 @@ msgstr "CAS"
msgid "Enable CAS Auth" msgid "Enable CAS Auth"
msgstr "CAS 認証の有効化" msgstr "CAS 認証の有効化"
#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 #: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:54
msgid "Server url" msgid "Server url"
msgstr "サービス側アドレス" msgstr "サービス側アドレス"
@@ -3723,11 +3723,11 @@ msgstr "クライアントID"
msgid "Client Secret" msgid "Client Secret"
msgstr "クライアント秘密" msgstr "クライアント秘密"
#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 #: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:68
msgid "Provider auth endpoint" msgid "Provider auth endpoint"
msgstr "認証エンドポイントアドレス" msgstr "認証エンドポイントアドレス"
#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 #: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:71
msgid "Provider token endpoint" msgid "Provider token endpoint"
msgstr "プロバイダートークンエンドポイント" msgstr "プロバイダートークンエンドポイント"
@@ -3735,15 +3735,15 @@ msgstr "プロバイダートークンエンドポイント"
msgid "Client authentication method" msgid "Client authentication method"
msgstr "クライアント認証方式" msgstr "クライアント認証方式"
#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 #: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:77
msgid "Provider userinfo endpoint" msgid "Provider userinfo endpoint"
msgstr "プロバイダーuserinfoエンドポイント" msgstr "プロバイダーuserinfoエンドポイント"
#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 #: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:80
msgid "Provider end session endpoint" msgid "Provider end session endpoint"
msgstr "プロバイダーのセッション終了エンドポイント" msgstr "プロバイダーのセッション終了エンドポイント"
#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 #: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:98
#: settings/serializers/auth/saml2.py:35 #: settings/serializers/auth/saml2.py:35
msgid "Always update user" msgid "Always update user"
msgstr "常にユーザーを更新" msgstr "常にユーザーを更新"
@@ -3772,51 +3772,59 @@ msgstr ""
"ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピン" "ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピン"
"グする方法、username, name,emailはjumpserverのユーザーが必要とする属性です" "グする方法、username, name,emailはjumpserverのユーザーが必要とする属性です"
#: settings/serializers/auth/oidc.py:46 #: settings/serializers/auth/oidc.py:41
msgid "Enable PKCE"
msgstr "启启PKCE"
#: settings/serializers/auth/oidc.py:43
msgid "Code challenge method"
msgstr "Code暗号化方式です"
#: settings/serializers/auth/oidc.py:51
msgid "Use Keycloak" msgid "Use Keycloak"
msgstr "Keycloakを使用する" msgstr "Keycloakを使用する"
#: settings/serializers/auth/oidc.py:52 #: settings/serializers/auth/oidc.py:57
msgid "Realm name" msgid "Realm name"
msgstr "レルム名" msgstr "レルム名"
#: settings/serializers/auth/oidc.py:58 #: settings/serializers/auth/oidc.py:63
msgid "Enable OPENID Auth" msgid "Enable OPENID Auth"
msgstr "OIDC認証の有効化" msgstr "OIDC認証の有効化"
#: settings/serializers/auth/oidc.py:60 #: settings/serializers/auth/oidc.py:65
msgid "Provider endpoint" msgid "Provider endpoint"
msgstr "プロバイダーエンドポイント" msgstr "プロバイダーエンドポイント"
#: settings/serializers/auth/oidc.py:69 #: settings/serializers/auth/oidc.py:74
msgid "Provider jwks endpoint" msgid "Provider jwks endpoint"
msgstr "プロバイダーjwksエンドポイント" msgstr "プロバイダーjwksエンドポイント"
#: settings/serializers/auth/oidc.py:78 #: settings/serializers/auth/oidc.py:83
msgid "Provider sign alg" msgid "Provider sign alg"
msgstr "プロビダーサインalg" msgstr "プロビダーサインalg"
#: settings/serializers/auth/oidc.py:81 #: settings/serializers/auth/oidc.py:86
msgid "Provider sign key" msgid "Provider sign key"
msgstr "プロバイダ署名キー" msgstr "プロバイダ署名キー"
#: settings/serializers/auth/oidc.py:83 #: settings/serializers/auth/oidc.py:88
msgid "Scopes" msgid "Scopes"
msgstr "スコープ" msgstr "スコープ"
#: settings/serializers/auth/oidc.py:85 #: settings/serializers/auth/oidc.py:90
msgid "Id token max age" msgid "Id token max age"
msgstr "IDトークンの最大年齢" msgstr "IDトークンの最大年齢"
#: settings/serializers/auth/oidc.py:88 #: settings/serializers/auth/oidc.py:93
msgid "Id token include claims" msgid "Id token include claims"
msgstr "IDトークンにはクレームが含まれます" msgstr "IDトークンにはクレームが含まれます"
#: settings/serializers/auth/oidc.py:90 #: settings/serializers/auth/oidc.py:95
msgid "Use state" msgid "Use state"
msgstr "使用状態" msgstr "使用状態"
#: settings/serializers/auth/oidc.py:91 #: settings/serializers/auth/oidc.py:96
msgid "Use nonce" msgid "Use nonce"
msgstr "Nonceを使用" msgstr "Nonceを使用"
@@ -5139,15 +5147,15 @@ msgstr "スレッド"
msgid "Boot Time" msgid "Boot Time"
msgstr "ブート時間" msgstr "ブート時間"
#: terminal/models/storage.py:29 #: terminal/models/storage.py:28
msgid "Default storage" msgid "Default storage"
msgstr "デフォルトのストレージ" msgstr "デフォルトのストレージ"
#: terminal/models/storage.py:139 terminal/models/terminal.py:109 #: terminal/models/storage.py:141 terminal/models/terminal.py:109
msgid "Command storage" msgid "Command storage"
msgstr "コマンドストレージ" msgstr "コマンドストレージ"
#: terminal/models/storage.py:199 terminal/models/terminal.py:110 #: terminal/models/storage.py:201 terminal/models/terminal.py:110
msgid "Replay storage" msgid "Replay storage"
msgstr "再生ストレージ" msgstr "再生ストレージ"
@@ -5444,19 +5452,19 @@ msgstr ""
"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: " "チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: "
"{}" "{}"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "Change field" msgid "Change field"
msgstr "フィールドを変更" msgstr "フィールドを変更"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "Before change" msgid "Before change"
msgstr "変更前" msgstr "変更前"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "After change" msgid "After change"
msgstr "変更後" msgstr "変更後"
#: tickets/handlers/base.py:96 #: tickets/handlers/base.py:98
msgid "{} {} the ticket" msgid "{} {} the ticket"
msgstr "{} {} チケット" msgstr "{} {} チケット"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-17 17:34+0800\n" "POT-Creation-Date: 2022-12-13 15:00+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@@ -23,13 +23,13 @@ msgstr "访问控制"
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
#: applications/models/application.py:219 assets/models/asset.py:138 #: applications/models/application.py:219 assets/models/asset.py:138
#: assets/models/base.py:175 assets/models/cluster.py:18 #: assets/models/base.py:173 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:27 assets/models/domain.py:23 #: assets/models/cmd_filter.py:27 assets/models/domain.py:23
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 #: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29
#: settings/models.py:33 settings/serializers/sms.py:6 #: settings/models.py:33 settings/serializers/sms.py:6
#: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87 #: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87
#: terminal/models/storage.py:27 terminal/models/task.py:16 #: terminal/models/storage.py:26 terminal/models/task.py:16
#: terminal/models/terminal.py:101 users/forms/profile.py:33 #: terminal/models/terminal.py:101 users/forms/profile.py:33
#: users/models/group.py:15 users/models/user.py:673 #: users/models/group.py:15 users/models/user.py:673
#: xpack/plugins/cloud/models.py:27 #: xpack/plugins/cloud/models.py:27
@@ -54,14 +54,14 @@ msgstr "激活中"
#: acls/models/base.py:32 applications/models/application.py:232 #: acls/models/base.py:32 applications/models/application.py:232
#: assets/models/asset.py:143 assets/models/asset.py:231 #: assets/models/asset.py:143 assets/models/asset.py:231
#: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/backup.py:54 assets/models/base.py:178
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:52 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:52
#: assets/models/cmd_filter.py:100 assets/models/domain.py:24 #: assets/models/cmd_filter.py:100 assets/models/domain.py:24
#: assets/models/domain.py:65 assets/models/group.py:23 #: assets/models/domain.py:65 assets/models/group.py:23
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38
#: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97 #: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97
#: terminal/models/storage.py:30 terminal/models/terminal.py:115 #: terminal/models/storage.py:29 terminal/models/terminal.py:115
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
#: users/models/group.py:16 users/models/user.py:712 #: users/models/group.py:16 users/models/user.py:712
#: xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/change_auth_plan/models/base.py:44
@@ -154,7 +154,7 @@ msgid "Format for comma-delimited string, with * indicating a match all. "
msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:174
#: assets/models/gathered_user.py:15 audits/models.py:139 #: assets/models/gathered_user.py:15 audits/models.py:139
#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260 #: authentication/models.py:260
@@ -305,7 +305,7 @@ msgstr "类别"
#: assets/models/cmd_filter.py:86 assets/models/user.py:251 #: assets/models/cmd_filter.py:86 assets/models/user.py:251
#: authentication/models.py:70 perms/models/application_permission.py:24 #: authentication/models.py:70 perms/models/application_permission.py:24
#: perms/serializers/application/user_permission.py:34 #: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:59 terminal/models/storage.py:145 #: terminal/models/storage.py:58 terminal/models/storage.py:147
#: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/comment.py:26 tickets/models/flow.py:57
#: tickets/models/ticket/apply_application.py:18 #: tickets/models/ticket/apply_application.py:18
#: tickets/models/ticket/general.py:273 #: tickets/models/ticket/general.py:273
@@ -348,7 +348,7 @@ msgid "Type display"
msgstr "类型名称" msgstr "类型名称"
#: applications/serializers/application.py:105 assets/models/asset.py:230 #: applications/serializers/application.py:105 assets/models/asset.py:230
#: assets/models/base.py:181 assets/models/cluster.py:26 #: assets/models/base.py:179 assets/models/cluster.py:26
#: assets/models/cmd_filter.py:53 assets/models/domain.py:26 #: assets/models/cmd_filter.py:53 assets/models/domain.py:26
#: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/account.py:18 #: assets/models/label.py:25 assets/serializers/account.py:18
@@ -362,7 +362,7 @@ msgstr "类型名称"
msgid "Date created" msgid "Date created"
msgstr "创建日期" msgstr "创建日期"
#: applications/serializers/application.py:106 assets/models/base.py:182 #: applications/serializers/application.py:106 assets/models/base.py:180
#: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20 #: assets/models/cmd_filter.py:54 assets/models/gathered_user.py:20
#: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29 #: assets/serializers/account.py:21 assets/serializers/cmd_filter.py:29
#: assets/serializers/cmd_filter.py:49 common/db/models.py:117 #: assets/serializers/cmd_filter.py:49 common/db/models.py:117
@@ -643,7 +643,7 @@ msgstr "资产编号"
msgid "Labels" msgid "Labels"
msgstr "标签管理" msgstr "标签管理"
#: assets/models/asset.py:229 assets/models/base.py:183 #: assets/models/asset.py:229 assets/models/base.py:181
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:56 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:56
#: assets/models/cmd_filter.py:103 assets/models/group.py:21 #: assets/models/cmd_filter.py:103 assets/models/group.py:21
#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71 #: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71
@@ -779,32 +779,32 @@ msgstr "是否成功"
msgid "Account backup execution" msgid "Account backup execution"
msgstr "账号备份执行" msgstr "账号备份执行"
#: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 #: assets/models/base.py:28 assets/tasks/const.py:51 audits/const.py:5
#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37
#: common/utils/ip/utils.py:84 #: common/utils/ip/utils.py:84
msgid "Unknown" msgid "Unknown"
msgstr "未知" msgstr "未知"
#: assets/models/base.py:31 #: assets/models/base.py:29
msgid "Ok" msgid "Ok"
msgstr "成功" msgstr "成功"
#: assets/models/base.py:32 audits/models.py:136 #: assets/models/base.py:30 audits/models.py:136
#: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/app.py:88
#: xpack/plugins/change_auth_plan/serializers/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:199
#: xpack/plugins/cloud/const.py:41 #: xpack/plugins/cloud/const.py:41
msgid "Failed" msgid "Failed"
msgstr "失败" msgstr "失败"
#: assets/models/base.py:38 assets/serializers/domain.py:47 #: assets/models/base.py:36 assets/serializers/domain.py:47
msgid "Connectivity" msgid "Connectivity"
msgstr "可连接性" msgstr "可连接性"
#: assets/models/base.py:40 authentication/models.py:263 #: assets/models/base.py:38 authentication/models.py:263
msgid "Date verified" msgid "Date verified"
msgstr "校验日期" msgstr "校验日期"
#: assets/models/base.py:177 assets/serializers/base.py:15 #: assets/models/base.py:175 assets/serializers/base.py:14
#: assets/serializers/base.py:37 assets/serializers/system_user.py:29 #: assets/serializers/base.py:37 assets/serializers/system_user.py:29
#: audits/signal_handlers.py:58 authentication/confirm/password.py:9 #: audits/signal_handlers.py:58 authentication/confirm/password.py:9
#: authentication/forms.py:32 #: authentication/forms.py:32
@@ -822,14 +822,14 @@ msgstr "校验日期"
msgid "Password" msgid "Password"
msgstr "密码" msgstr "密码"
#: assets/models/base.py:178 assets/serializers/base.py:41 #: assets/models/base.py:176 assets/serializers/base.py:41
#: xpack/plugins/change_auth_plan/models/asset.py:53 #: xpack/plugins/change_auth_plan/models/asset.py:53
#: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:130
#: xpack/plugins/change_auth_plan/models/asset.py:206 #: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key" msgid "SSH private key"
msgstr "SSH密钥" msgstr "SSH密钥"
#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:56 #: assets/models/base.py:177 xpack/plugins/change_auth_plan/models/asset.py:56
#: xpack/plugins/change_auth_plan/models/asset.py:126 #: xpack/plugins/change_auth_plan/models/asset.py:126
#: xpack/plugins/change_auth_plan/models/asset.py:202 #: xpack/plugins/change_auth_plan/models/asset.py:202
msgid "SSH public key" msgid "SSH public key"
@@ -1182,7 +1182,7 @@ msgstr "ssh私钥"
msgid "Key password" msgid "Key password"
msgstr "密钥密码" msgstr "密钥密码"
#: assets/serializers/base.py:58 #: assets/serializers/base.py:58 assets/serializers/utils.py:24
msgid "private key invalid or passphrase error" msgid "private key invalid or passphrase error"
msgstr "密钥不合法或密钥密码错误" msgstr "密钥不合法或密钥密码错误"
@@ -1295,15 +1295,15 @@ msgstr "组织名称"
msgid "Asset hostname" msgid "Asset hostname"
msgstr "资产主机名" msgstr "资产主机名"
#: assets/serializers/utils.py:11 #: assets/serializers/utils.py:13
msgid "Password can not contains `{{` " msgid "Password can not contains `{{` "
msgstr "密码不能包含 `{{` 字符" msgstr "密码不能包含 `{{` 字符"
#: assets/serializers/utils.py:14 #: assets/serializers/utils.py:16
msgid "Password can not contains `'` " msgid "Password can not contains `'` "
msgstr "密码不能包含 `'` 字符" msgstr "密码不能包含 `'` 字符"
#: assets/serializers/utils.py:16 #: assets/serializers/utils.py:18
msgid "Password can not contains `\"` " msgid "Password can not contains `\"` "
msgstr "密码不能包含 `\"` 字符" msgstr "密码不能包含 `\"` 字符"
@@ -2233,7 +2233,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_reset_password_code.html:9 #: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:413 ops/tasks.py:145 ops/tasks.py:148 #: jumpserver/conf.py:416 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33 #: tickets/templates/tickets/approve_check_password.html:33
@@ -2708,11 +2708,11 @@ msgstr "不能包含特殊字符"
msgid "The mobile phone number format is incorrect" msgid "The mobile phone number format is incorrect"
msgstr "手机号格式不正确" msgstr "手机号格式不正确"
#: jumpserver/conf.py:412 #: jumpserver/conf.py:415
msgid "Create account successfully" msgid "Create account successfully"
msgstr "创建账号成功" msgstr "创建账号成功"
#: jumpserver/conf.py:414 #: jumpserver/conf.py:417
msgid "Your account has been created successfully" msgid "Your account has been created successfully"
msgstr "你的账号已创建成功" msgstr "你的账号已创建成功"
@@ -3555,7 +3555,7 @@ msgstr "CAS"
msgid "Enable CAS Auth" msgid "Enable CAS Auth"
msgstr "启用 CAS 认证" msgstr "启用 CAS 认证"
#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 #: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:54
msgid "Server url" msgid "Server url"
msgstr "服务端地址" msgstr "服务端地址"
@@ -3675,11 +3675,11 @@ msgstr "客户端 ID"
msgid "Client Secret" msgid "Client Secret"
msgstr "客户端密钥" msgstr "客户端密钥"
#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 #: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:68
msgid "Provider auth endpoint" msgid "Provider auth endpoint"
msgstr "授权端点地址" msgstr "授权端点地址"
#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 #: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:71
msgid "Provider token endpoint" msgid "Provider token endpoint"
msgstr "token 端点地址" msgstr "token 端点地址"
@@ -3687,15 +3687,15 @@ msgstr "token 端点地址"
msgid "Client authentication method" msgid "Client authentication method"
msgstr "客户端认证方式" msgstr "客户端认证方式"
#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 #: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:77
msgid "Provider userinfo endpoint" msgid "Provider userinfo endpoint"
msgstr "用户信息端点地址" msgstr "用户信息端点地址"
#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 #: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:80
msgid "Provider end session endpoint" msgid "Provider end session endpoint"
msgstr "注销会话端点地址" msgstr "注销会话端点地址"
#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 #: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:98
#: settings/serializers/auth/saml2.py:35 #: settings/serializers/auth/saml2.py:35
msgid "Always update user" msgid "Always update user"
msgstr "总是更新用户信息" msgstr "总是更新用户信息"
@@ -3724,51 +3724,59 @@ msgstr ""
"用户属性映射代表怎样将OpenID中用户属性映射到jumpserver用户上username, name," "用户属性映射代表怎样将OpenID中用户属性映射到jumpserver用户上username, name,"
"email 是jumpserver的用户需要属性" "email 是jumpserver的用户需要属性"
#: settings/serializers/auth/oidc.py:46 #: settings/serializers/auth/oidc.py:41
msgid "Enable PKCE"
msgstr "启用 PKCE"
#: settings/serializers/auth/oidc.py:43
msgid "Code challenge method"
msgstr "Code加密方式"
#: settings/serializers/auth/oidc.py:51
msgid "Use Keycloak" msgid "Use Keycloak"
msgstr "使用 Keycloak" msgstr "使用 Keycloak"
#: settings/serializers/auth/oidc.py:52 #: settings/serializers/auth/oidc.py:57
msgid "Realm name" msgid "Realm name"
msgstr "域" msgstr "域"
#: settings/serializers/auth/oidc.py:58 #: settings/serializers/auth/oidc.py:63
msgid "Enable OPENID Auth" msgid "Enable OPENID Auth"
msgstr "启用 OIDC 认证" msgstr "启用 OIDC 认证"
#: settings/serializers/auth/oidc.py:60 #: settings/serializers/auth/oidc.py:65
msgid "Provider endpoint" msgid "Provider endpoint"
msgstr "端点地址" msgstr "端点地址"
#: settings/serializers/auth/oidc.py:69 #: settings/serializers/auth/oidc.py:74
msgid "Provider jwks endpoint" msgid "Provider jwks endpoint"
msgstr "jwks 端点地址" msgstr "jwks 端点地址"
#: settings/serializers/auth/oidc.py:78 #: settings/serializers/auth/oidc.py:83
msgid "Provider sign alg" msgid "Provider sign alg"
msgstr "签名算法" msgstr "签名算法"
#: settings/serializers/auth/oidc.py:81 #: settings/serializers/auth/oidc.py:86
msgid "Provider sign key" msgid "Provider sign key"
msgstr "签名 Key" msgstr "签名 Key"
#: settings/serializers/auth/oidc.py:83 #: settings/serializers/auth/oidc.py:88
msgid "Scopes" msgid "Scopes"
msgstr "连接范围" msgstr "连接范围"
#: settings/serializers/auth/oidc.py:85 #: settings/serializers/auth/oidc.py:90
msgid "Id token max age" msgid "Id token max age"
msgstr "令牌有效时间" msgstr "令牌有效时间"
#: settings/serializers/auth/oidc.py:88 #: settings/serializers/auth/oidc.py:93
msgid "Id token include claims" msgid "Id token include claims"
msgstr "声明" msgstr "声明"
#: settings/serializers/auth/oidc.py:90 #: settings/serializers/auth/oidc.py:95
msgid "Use state" msgid "Use state"
msgstr "使用状态" msgstr "使用状态"
#: settings/serializers/auth/oidc.py:91 #: settings/serializers/auth/oidc.py:96
msgid "Use nonce" msgid "Use nonce"
msgstr "临时使用" msgstr "临时使用"
@@ -5053,15 +5061,15 @@ msgstr "线程数"
msgid "Boot Time" msgid "Boot Time"
msgstr "运行时间" msgstr "运行时间"
#: terminal/models/storage.py:29 #: terminal/models/storage.py:28
msgid "Default storage" msgid "Default storage"
msgstr "默认存储" msgstr "默认存储"
#: terminal/models/storage.py:139 terminal/models/terminal.py:109 #: terminal/models/storage.py:141 terminal/models/terminal.py:109
msgid "Command storage" msgid "Command storage"
msgstr "命令存储" msgstr "命令存储"
#: terminal/models/storage.py:199 terminal/models/terminal.py:110 #: terminal/models/storage.py:201 terminal/models/terminal.py:110
msgid "Replay storage" msgid "Replay storage"
msgstr "录像存储" msgstr "录像存储"
@@ -5351,19 +5359,19 @@ msgid ""
msgstr "" msgstr ""
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" "通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "Change field" msgid "Change field"
msgstr "变更字段" msgstr "变更字段"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "Before change" msgid "Before change"
msgstr "变更前" msgstr "变更前"
#: tickets/handlers/base.py:84 #: tickets/handlers/base.py:86
msgid "After change" msgid "After change"
msgstr "变更后" msgstr "变更后"
#: tickets/handlers/base.py:96 #: tickets/handlers/base.py:98
msgid "{} {} the ticket" msgid "{} {} the ticket"
msgstr "{} {} 工单" msgstr "{} {} 工单"

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
from html import escape
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.template.loader import render_to_string from django.template.loader import render_to_string
@@ -96,11 +98,19 @@ class BaseHandler:
approve_info = _('{} {} the ticket').format(user_display, state_display) approve_info = _('{} {} the ticket').format(user_display, state_display)
context = self._diff_prev_approve_context(state) context = self._diff_prev_approve_context(state)
context.update({'approve_info': approve_info}) context.update({'approve_info': approve_info})
body = self.reject_html_script(
render_to_string('tickets/ticket_approve_diff.html', context)
)
data = { data = {
'body': render_to_string('tickets/ticket_approve_diff.html', context), 'body': body,
'user': user, 'user': user,
'user_display': str(user), 'user_display': str(user),
'type': 'state', 'type': 'state',
'state': state 'state': state
} }
return self.ticket.comments.create(**data) return self.ticket.comments.create(**data)
@staticmethod
def reject_html_script(unsafe_html):
safe_html = escape(unsafe_html)
return safe_html

View File

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

View File

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