mirror of https://github.com/jumpserver/jumpserver
commit
9b60d86ddd
|
@ -0,0 +1,9 @@
|
|||
# 安全说明
|
||||
|
||||
JumpServer 是一款正在成长的安全产品, 请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/) 部署安装.
|
||||
|
||||
如果你发现安全问题,请直接联系我们,我们携手让世界更好:
|
||||
|
||||
- ibuler@fit2cloud.com
|
||||
- support@fit2cloud.com
|
||||
- 400-052-0755
|
|
@ -9,12 +9,11 @@ from tickets.api import GenericTicketStatusRetrieveCloseAPI
|
|||
from ..models import LoginAssetACL
|
||||
from .. import serializers
|
||||
|
||||
|
||||
__all__ = ['LoginAssetCheckAPI', 'LoginAssetConfirmStatusAPI']
|
||||
|
||||
|
||||
class LoginAssetCheckAPI(CreateAPIView):
|
||||
permission_classes = (IsAppUser, )
|
||||
permission_classes = (IsAppUser,)
|
||||
serializer_class = serializers.LoginAssetCheckSerializer
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
@ -57,11 +56,12 @@ class LoginAssetCheckAPI(CreateAPIView):
|
|||
external=True, api_to_ui=True
|
||||
)
|
||||
ticket_detail_url = '{url}?type={type}'.format(url=ticket_detail_url, type=ticket.type)
|
||||
ticket_assignees = ticket.current_node.first().ticket_assignees.all()
|
||||
data = {
|
||||
'check_confirm_status': {'method': 'GET', 'url': confirm_status_url},
|
||||
'close_confirm': {'method': 'DELETE', 'url': confirm_status_url},
|
||||
'ticket_detail_url': ticket_detail_url,
|
||||
'reviewers': [str(user) for user in ticket.current_node.first().ticket_assignees.all()],
|
||||
'reviewers': [str(ticket_assignee.assignee) for ticket_assignee in ticket_assignees]
|
||||
}
|
||||
return data
|
||||
|
||||
|
@ -74,4 +74,3 @@ class LoginAssetCheckAPI(CreateAPIView):
|
|||
|
||||
class LoginAssetConfirmStatusAPI(GenericTicketStatusRetrieveCloseAPI):
|
||||
pass
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ from ..hands import IsOrgAdmin, IsAppUser
|
|||
from ..models import CommandFilter, CommandFilterRule
|
||||
from .. import serializers
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CommandFilterViewSet', 'CommandFilterRuleViewSet', 'CommandConfirmAPI',
|
||||
'CommandConfirmStatusAPI'
|
||||
|
@ -44,7 +43,7 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet):
|
|||
|
||||
|
||||
class CommandConfirmAPI(CreateAPIView):
|
||||
permission_classes = (IsAppUser, )
|
||||
permission_classes = (IsAppUser,)
|
||||
serializer_class = serializers.CommandConfirmSerializer
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
@ -73,11 +72,12 @@ class CommandConfirmAPI(CreateAPIView):
|
|||
external=True, api_to_ui=True
|
||||
)
|
||||
ticket_detail_url = '{url}?type={type}'.format(url=ticket_detail_url, type=ticket.type)
|
||||
ticket_assignees = ticket.current_node.first().ticket_assignees.all()
|
||||
return {
|
||||
'check_confirm_status': {'method': 'GET', 'url': confirm_status_url},
|
||||
'close_confirm': {'method': 'DELETE', 'url': confirm_status_url},
|
||||
'ticket_detail_url': ticket_detail_url,
|
||||
'reviewers': [str(user) for user in ticket.current_node.first().ticket_assignees.all()]
|
||||
'reviewers': [str(ticket_assignee.assignee) for ticket_assignee in ticket_assignees]
|
||||
}
|
||||
|
||||
@lazyproperty
|
||||
|
@ -89,4 +89,3 @@ class CommandConfirmAPI(CreateAPIView):
|
|||
|
||||
class CommandConfirmStatusAPI(GenericTicketStatusRetrieveCloseAPI):
|
||||
pass
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ def migrate_old_authbook_to_history(apps, schema_editor):
|
|||
|
||||
print()
|
||||
while True:
|
||||
authbooks = authbook_model.objects.using(db_alias).filter(is_latest=False)[:20]
|
||||
authbooks = authbook_model.objects.using(db_alias).filter(is_latest=False)[:1000]
|
||||
if not authbooks:
|
||||
break
|
||||
historys = []
|
||||
|
|
|
@ -5,9 +5,12 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from simple_history.models import HistoricalRecords
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from common.utils import lazyproperty, get_logger
|
||||
from .base import BaseUser, AbsConnectivity
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
__all__ = ['AuthBook']
|
||||
|
||||
|
||||
|
@ -93,6 +96,24 @@ class AuthBook(BaseUser, AbsConnectivity):
|
|||
i.comment = 'Update triggered by account {}'.format(self.id)
|
||||
i.save(update_fields=['password', 'private_key', 'public_key'])
|
||||
|
||||
def remove_asset_admin_user_if_need(self):
|
||||
if not self.asset or not self.asset.admin_user:
|
||||
return
|
||||
if not self.systemuser.is_admin_user:
|
||||
return
|
||||
logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser))
|
||||
self.asset.admin_user = None
|
||||
self.asset.save()
|
||||
|
||||
def update_asset_admin_user_if_need(self):
|
||||
if not self.systemuser or not self.systemuser.is_admin_user:
|
||||
return
|
||||
if not self.asset or self.asset.admin_user == self.systemuser:
|
||||
return
|
||||
logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser))
|
||||
self.asset.admin_user = self.systemuser
|
||||
self.asset.save()
|
||||
|
||||
def __str__(self):
|
||||
return self.smart_name
|
||||
|
||||
|
|
|
@ -75,10 +75,13 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
fields_mini = ['id', 'hostname', 'ip', 'platform', 'protocols']
|
||||
fields_small = fields_mini + [
|
||||
'protocol', 'port', 'protocols', 'is_active', 'public_ip',
|
||||
'comment',
|
||||
]
|
||||
hardware_fields = [
|
||||
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||
'os', 'os_version', 'os_arch', 'hostname_raw', 'comment',
|
||||
'hardware_info', 'connectivity', 'date_verified'
|
||||
'os', 'os_version', 'os_arch', 'hostname_raw', 'hardware_info',
|
||||
'connectivity', 'date_verified'
|
||||
]
|
||||
fields_fk = [
|
||||
'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display'
|
||||
|
@ -89,15 +92,16 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
read_only_fields = [
|
||||
'created_by', 'date_created',
|
||||
]
|
||||
fields = fields_small + fields_fk + fields_m2m + read_only_fields
|
||||
fields = fields_small + hardware_fields + fields_fk + fields_m2m + read_only_fields
|
||||
|
||||
extra_kwargs = {
|
||||
extra_kwargs = {k: {'read_only': True} for k in hardware_fields}
|
||||
extra_kwargs.update({
|
||||
'protocol': {'write_only': True},
|
||||
'port': {'write_only': True},
|
||||
'hardware_info': {'label': _('Hardware info')},
|
||||
'org_name': {'label': _('Org name')},
|
||||
'admin_user_display': {'label': _('Admin user display')}
|
||||
}
|
||||
'hardware_info': {'label': _('Hardware info'), 'read_only': True},
|
||||
'org_name': {'label': _('Org name'), 'read_only': True},
|
||||
'admin_user_display': {'label': _('Admin user display'), 'read_only': True},
|
||||
})
|
||||
|
||||
def get_fields(self):
|
||||
fields = super().get_fields()
|
||||
|
|
|
@ -38,7 +38,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
'username_same_with_user', 'auto_push', 'auto_generate_key',
|
||||
'date_created', 'date_updated', 'comment', 'created_by',
|
||||
]
|
||||
fields_m2m = ['cmd_filters', 'assets_amount']
|
||||
fields_m2m = ['cmd_filters', 'assets_amount', 'nodes']
|
||||
fields = fields_small + fields_m2m
|
||||
extra_kwargs = {
|
||||
'password': {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.dispatch import receiver
|
||||
from django.apps import apps
|
||||
from simple_history.signals import pre_create_historical_record
|
||||
from django.db.models.signals import post_save, pre_save
|
||||
from django.db.models.signals import post_save, pre_save, post_delete
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..models import AuthBook, SystemUser
|
||||
|
@ -28,10 +28,15 @@ def pre_create_historical_record_callback(sender, history_instance=None, **kwarg
|
|||
setattr(history_instance, attr, system_user_attr_value)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=AuthBook)
|
||||
def on_authbook_post_delete(sender, instance, **kwargs):
|
||||
instance.remove_asset_admin_user_if_need()
|
||||
|
||||
|
||||
@receiver(post_save, sender=AuthBook)
|
||||
def on_authbook_post_create(sender, instance, **kwargs):
|
||||
if not instance.systemuser:
|
||||
instance.sync_to_system_user_account()
|
||||
instance.sync_to_system_user_account()
|
||||
instance.update_asset_admin_user_if_need()
|
||||
|
||||
|
||||
@receiver(pre_save, sender=AuthBook)
|
||||
|
|
|
@ -23,6 +23,7 @@ from common.drf.api import SerializerMixin
|
|||
from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser
|
||||
from orgs.mixins.api import RootOrgViewMixin
|
||||
from common.http import is_true
|
||||
from assets.models import SystemUser
|
||||
|
||||
from ..serializers import (
|
||||
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
|
||||
|
@ -88,6 +89,9 @@ class ClientProtocolMixin:
|
|||
drives_redirect = is_true(self.request.query_params.get('drives_redirect'))
|
||||
token = self.create_token(user, asset, application, system_user)
|
||||
|
||||
if system_user.login_mode == SystemUser.LOGIN_MANUAL:
|
||||
options['prompt for credentials on client:i'] = '1'
|
||||
|
||||
if drives_redirect:
|
||||
options['drivestoredirect:s'] = '*'
|
||||
options['screen mode id:i'] = '2' if full_screen else '1'
|
||||
|
|
|
@ -30,8 +30,8 @@ logger = get_logger(__name__)
|
|||
def check_backend_can_auth(username, backend_path, allowed_auth_backends):
|
||||
if allowed_auth_backends is not None and backend_path not in allowed_auth_backends:
|
||||
logger.debug('Skip user auth backend: {}, {} not in'.format(
|
||||
username, backend_path, ','.join(allowed_auth_backends)
|
||||
)
|
||||
username, backend_path, ','.join(allowed_auth_backends)
|
||||
)
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
@ -201,7 +201,7 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
|||
data = request.POST
|
||||
|
||||
items = ['username', 'password', 'challenge', 'public_key', 'auto_login']
|
||||
username, password, challenge, public_key, auto_login = bulk_get(data, *items, default='')
|
||||
username, password, challenge, public_key, auto_login = bulk_get(data, *items, default='')
|
||||
ip = self.get_request_ip()
|
||||
self._set_partial_credential_error(username=username, ip=ip, request=request)
|
||||
|
||||
|
@ -285,7 +285,6 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
|||
elif not user.is_active:
|
||||
self.raise_credential_error(errors.reason_user_inactive)
|
||||
|
||||
self._check_is_local_user(user)
|
||||
self._check_is_block(user.username)
|
||||
self._check_login_acl(user, ip)
|
||||
|
||||
|
@ -363,6 +362,12 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
|||
self.request.session['auth_mfa_required'] = ''
|
||||
self.request.session['auth_mfa_type'] = mfa_type
|
||||
|
||||
def clean_mfa_mark(self):
|
||||
self.request.session['auth_mfa'] = ''
|
||||
self.request.session['auth_mfa_time'] = ''
|
||||
self.request.session['auth_mfa_required'] = ''
|
||||
self.request.session['auth_mfa_type'] = ''
|
||||
|
||||
def check_mfa_is_block(self, username, ip, raise_exception=True):
|
||||
if MFABlockUtils(username, ip).is_block():
|
||||
logger.warn('Ip was blocked' + ': ' + username + ':' + ip)
|
||||
|
@ -414,10 +419,12 @@ class AuthMixin(PasswordEncryptionViewMixin):
|
|||
self.request.session["auth_confirm"] = "1"
|
||||
return
|
||||
elif ticket.state_reject:
|
||||
self.clean_mfa_mark()
|
||||
raise errors.LoginConfirmOtherError(
|
||||
ticket.id, ticket.get_state_display()
|
||||
)
|
||||
elif ticket.state_close:
|
||||
self.clean_mfa_mark()
|
||||
raise errors.LoginConfirmOtherError(
|
||||
ticket.id, ticket.get_state_display()
|
||||
)
|
||||
|
|
|
@ -134,6 +134,7 @@ function cancelTicket() {
|
|||
}
|
||||
|
||||
function cancelCloseConfirm() {
|
||||
cancelTicket();
|
||||
window.onbeforeunload = function() {};
|
||||
window.onunload = function(){};
|
||||
}
|
||||
|
|
|
@ -215,7 +215,7 @@ class DingTalkQRLoginCallbackView(AuthMixin, DingTalkQRMixin, View):
|
|||
user = get_object_or_none(User, dingtalk_id=userid)
|
||||
if user is None:
|
||||
title = _('DingTalk is not bound')
|
||||
msg = _('Please login with a password and then bind the WeCom')
|
||||
msg = _('Please login with a password and then bind the DingTalk')
|
||||
response = self.get_failed_reponse(login_url, title=title, msg=msg)
|
||||
return response
|
||||
|
||||
|
|
|
@ -82,6 +82,15 @@ class TencentSMS(BaseSMSClient):
|
|||
|
||||
resp = self.client.SendSms(req)
|
||||
|
||||
try:
|
||||
code = resp.SendStatusSet[0].Code
|
||||
msg = resp.SendStatusSet[0].Message
|
||||
except IndexError:
|
||||
raise JMSException(code='response_bad', detail=resp)
|
||||
|
||||
if code.lower() != 'ok':
|
||||
raise JMSException(code=code, detail=msg)
|
||||
|
||||
return resp
|
||||
except TencentCloudSDKException as e:
|
||||
raise JMSException(code=e.code, detail=e.message)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import re
|
||||
|
||||
from django.core.validators import RegexValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.validators import (
|
||||
|
@ -32,3 +34,12 @@ class NoSpecialChars:
|
|||
raise serializers.ValidationError(
|
||||
_("Should not contains special characters")
|
||||
)
|
||||
|
||||
|
||||
class PhoneValidator:
|
||||
pattern = re.compile(r"^1[356789]\d{9}$")
|
||||
message = _('The mobile phone number format is incorrect')
|
||||
|
||||
def __call__(self, value):
|
||||
if not self.pattern.match(value):
|
||||
raise serializers.ValidationError(self.message)
|
||||
|
|
|
@ -212,12 +212,12 @@ class Config(dict):
|
|||
# Cas 认证
|
||||
'AUTH_CAS': False,
|
||||
'CAS_SERVER_URL': "https://example.com/cas/",
|
||||
'CAS_ROOT_PROXIED_AS': '',
|
||||
'CAS_ROOT_PROXIED_AS': 'https://example.com',
|
||||
'CAS_LOGOUT_COMPLETELY': True,
|
||||
'CAS_VERSION': 3,
|
||||
'CAS_USERNAME_ATTRIBUTE': 'uid',
|
||||
'CAS_APPLY_ATTRIBUTES_TO_USER': False,
|
||||
'CAS_RENAME_ATTRIBUTES': {},
|
||||
'CAS_RENAME_ATTRIBUTES': {'uid': 'username'},
|
||||
'CAS_CREATE_USER': True,
|
||||
|
||||
'AUTH_SSO': False,
|
||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-09-13 20:32+0800\n"
|
||||
"POT-Creation-Date: 2021-09-15 20:51+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"
|
||||
|
@ -118,7 +118,7 @@ msgstr "系统用户"
|
|||
|
||||
#: acls/models/login_asset_acl.py:22
|
||||
#: applications/serializers/attrs/application_category/remote_app.py:37
|
||||
#: assets/models/asset.py:357 assets/models/authbook.py:15
|
||||
#: assets/models/asset.py:357 assets/models/authbook.py:18
|
||||
#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:207
|
||||
#: audits/models.py:38 perms/models/asset_permission.py:99
|
||||
#: templates/index.html:82 terminal/backends/command/models.py:19
|
||||
|
@ -246,7 +246,7 @@ msgstr "远程应用"
|
|||
msgid "Custom"
|
||||
msgstr "自定义"
|
||||
|
||||
#: applications/models/account.py:11 assets/models/authbook.py:16
|
||||
#: applications/models/account.py:11 assets/models/authbook.py:19
|
||||
#: assets/models/user.py:281 audits/models.py:39
|
||||
#: perms/models/application_permission.py:32
|
||||
#: perms/models/asset_permission.py:101 templates/_nav.html:45
|
||||
|
@ -263,8 +263,8 @@ msgstr "自定义"
|
|||
msgid "System user"
|
||||
msgstr "系统用户"
|
||||
|
||||
#: applications/models/account.py:12 assets/models/authbook.py:17
|
||||
#: settings/serializers/auth/cas.py:14
|
||||
#: applications/models/account.py:12 assets/models/authbook.py:20
|
||||
#: settings/serializers/auth/cas.py:15
|
||||
msgid "Version"
|
||||
msgstr "版本"
|
||||
|
||||
|
@ -421,7 +421,7 @@ msgstr "基础"
|
|||
msgid "Charset"
|
||||
msgstr "编码"
|
||||
|
||||
#: assets/models/asset.py:142 assets/serializers/asset.py:168
|
||||
#: assets/models/asset.py:142 assets/serializers/asset.py:172
|
||||
#: tickets/models/ticket.py:50
|
||||
msgid "Meta"
|
||||
msgstr "元数据"
|
||||
|
@ -548,7 +548,7 @@ msgstr "创建者"
|
|||
msgid "Date created"
|
||||
msgstr "创建日期"
|
||||
|
||||
#: assets/models/authbook.py:23
|
||||
#: assets/models/authbook.py:26
|
||||
msgid "AuthBook"
|
||||
msgstr "账号"
|
||||
|
||||
|
@ -834,16 +834,16 @@ msgstr "网域名称"
|
|||
msgid "Nodes name"
|
||||
msgstr "节点名称"
|
||||
|
||||
#: assets/serializers/asset.py:97
|
||||
#: assets/serializers/asset.py:101
|
||||
msgid "Hardware info"
|
||||
msgstr "硬件信息"
|
||||
|
||||
#: assets/serializers/asset.py:98 assets/serializers/system_user.py:225
|
||||
#: assets/serializers/asset.py:102 assets/serializers/system_user.py:225
|
||||
#: orgs/mixins/serializers.py:26
|
||||
msgid "Org name"
|
||||
msgstr "用户名"
|
||||
msgstr "组织名称"
|
||||
|
||||
#: assets/serializers/asset.py:99
|
||||
#: assets/serializers/asset.py:103
|
||||
msgid "Admin user display"
|
||||
msgstr "特权用户名称"
|
||||
|
||||
|
@ -1240,13 +1240,13 @@ msgstr "认证令牌"
|
|||
|
||||
#: audits/signals_handler.py:68
|
||||
#: authentication/templates/authentication/login.html:210
|
||||
#: notifications/backends/__init__.py:11
|
||||
#: notifications/backends/__init__.py:11 users/models/user.py:659
|
||||
msgid "WeCom"
|
||||
msgstr "企业微信"
|
||||
|
||||
#: audits/signals_handler.py:69
|
||||
#: authentication/templates/authentication/login.html:215
|
||||
#: notifications/backends/__init__.py:12
|
||||
#: notifications/backends/__init__.py:12 users/models/user.py:660
|
||||
msgid "DingTalk"
|
||||
msgstr "钉钉"
|
||||
|
||||
|
@ -1616,15 +1616,15 @@ msgstr "来源 IP 不被允许登录"
|
|||
msgid "SSO auth closed"
|
||||
msgstr "SSO 认证关闭了"
|
||||
|
||||
#: authentication/errors.py:298 authentication/mixins.py:319
|
||||
#: authentication/errors.py:298 authentication/mixins.py:318
|
||||
msgid "Your password is too simple, please change it for security"
|
||||
msgstr "你的密码过于简单,为了安全,请修改"
|
||||
|
||||
#: authentication/errors.py:307 authentication/mixins.py:326
|
||||
#: authentication/errors.py:307 authentication/mixins.py:325
|
||||
msgid "You should to change your password before login"
|
||||
msgstr "登录完成前,请先修改密码"
|
||||
|
||||
#: authentication/errors.py:316 authentication/mixins.py:333
|
||||
#: authentication/errors.py:316 authentication/mixins.py:332
|
||||
msgid "Your password has expired, please reset before logging in"
|
||||
msgstr "您的密码已过期,先修改再登录"
|
||||
|
||||
|
@ -1649,7 +1649,7 @@ msgstr "MFA 类型"
|
|||
msgid "MFA code"
|
||||
msgstr "多因子认证验证码"
|
||||
|
||||
#: authentication/mixins.py:309
|
||||
#: authentication/mixins.py:308
|
||||
msgid "Please change your password"
|
||||
msgstr "请修改密码"
|
||||
|
||||
|
@ -1657,7 +1657,7 @@ msgstr "请修改密码"
|
|||
msgid "Private Token"
|
||||
msgstr "SSH密钥"
|
||||
|
||||
#: authentication/models.py:49 settings/serializers/security.py:115
|
||||
#: authentication/models.py:49 settings/serializers/security.py:118
|
||||
msgid "Login Confirm"
|
||||
msgstr "登录复核"
|
||||
|
||||
|
@ -1788,7 +1788,7 @@ msgid "CAS"
|
|||
msgstr "CAS"
|
||||
|
||||
#: authentication/templates/authentication/login.html:220
|
||||
#: notifications/backends/__init__.py:14
|
||||
#: notifications/backends/__init__.py:14 users/models/user.py:661
|
||||
msgid "FeiShu"
|
||||
msgstr "飞书"
|
||||
|
||||
|
@ -1885,9 +1885,9 @@ msgstr "从钉钉获取用户失败"
|
|||
msgid "DingTalk is not bound"
|
||||
msgstr "钉钉没有绑定"
|
||||
|
||||
#: authentication/views/dingtalk.py:218 authentication/views/wecom.py:216
|
||||
msgid "Please login with a password and then bind the WeCom"
|
||||
msgstr "请使用密码登录,然后绑定企业微信"
|
||||
#: authentication/views/dingtalk.py:218
|
||||
msgid "Please login with a password and then bind the DingTalk"
|
||||
msgstr "请使用密码登录,然后绑定钉钉"
|
||||
|
||||
#: authentication/views/dingtalk.py:260 authentication/views/dingtalk.py:261
|
||||
msgid "Binding DingTalk failed"
|
||||
|
@ -1930,19 +1930,19 @@ msgstr "请使用密码登录,然后绑定飞书"
|
|||
msgid "Binding FeiShu failed"
|
||||
msgstr "绑定飞书失败"
|
||||
|
||||
#: authentication/views/login.py:78
|
||||
#: authentication/views/login.py:80
|
||||
msgid "Redirecting"
|
||||
msgstr "跳转中"
|
||||
|
||||
#: authentication/views/login.py:79
|
||||
#: authentication/views/login.py:81
|
||||
msgid "Redirecting to {} authentication"
|
||||
msgstr "正在跳转到 {} 认证"
|
||||
|
||||
#: authentication/views/login.py:105
|
||||
#: authentication/views/login.py:107
|
||||
msgid "Please enable cookies and try again."
|
||||
msgstr "设置你的浏览器支持cookie"
|
||||
|
||||
#: authentication/views/login.py:217
|
||||
#: authentication/views/login.py:219
|
||||
msgid ""
|
||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||
" Don't close this page"
|
||||
|
@ -1950,15 +1950,15 @@ msgstr ""
|
|||
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
||||
" 不要关闭本页面"
|
||||
|
||||
#: authentication/views/login.py:222
|
||||
#: authentication/views/login.py:224
|
||||
msgid "No ticket found"
|
||||
msgstr "没有发现工单"
|
||||
|
||||
#: authentication/views/login.py:254
|
||||
#: authentication/views/login.py:256
|
||||
msgid "Logout success"
|
||||
msgstr "退出登录成功"
|
||||
|
||||
#: authentication/views/login.py:255
|
||||
#: authentication/views/login.py:257
|
||||
msgid "Logout success, return login page"
|
||||
msgstr "退出登录成功,返回到登录页面"
|
||||
|
||||
|
@ -2003,6 +2003,10 @@ msgstr "从企业微信获取用户失败"
|
|||
msgid "WeCom is not bound"
|
||||
msgstr "没有绑定企业微信"
|
||||
|
||||
#: authentication/views/wecom.py:216
|
||||
msgid "Please login with a password and then bind the WeCom"
|
||||
msgstr "请使用密码登录,然后绑定企业微信"
|
||||
|
||||
#: authentication/views/wecom.py:258 authentication/views/wecom.py:259
|
||||
msgid "Binding WeCom failed"
|
||||
msgstr "绑定企业微信失败"
|
||||
|
@ -2134,18 +2138,22 @@ msgstr "忽略时间"
|
|||
msgid "Invalid ip"
|
||||
msgstr "无效IP"
|
||||
|
||||
#: common/validators.py:13
|
||||
#: common/validators.py:15
|
||||
msgid "Special char not allowed"
|
||||
msgstr "不能包含特殊字符"
|
||||
|
||||
#: common/validators.py:25
|
||||
#: common/validators.py:27
|
||||
msgid "This field must be unique."
|
||||
msgstr "字段必须唯一"
|
||||
|
||||
#: common/validators.py:33
|
||||
#: common/validators.py:35
|
||||
msgid "Should not contains special characters"
|
||||
msgstr "不能包含特殊字符"
|
||||
|
||||
#: common/validators.py:41
|
||||
msgid "The mobile phone number format is incorrect"
|
||||
msgstr "手机号格式不正确"
|
||||
|
||||
#: jumpserver/context_processor.py:20
|
||||
msgid "JumpServer Open Source Bastion Host"
|
||||
msgstr "JumpServer 开源堡垒机"
|
||||
|
@ -2334,22 +2342,22 @@ msgstr "任务结束"
|
|||
msgid "Server performance"
|
||||
msgstr "监控告警"
|
||||
|
||||
#: ops/notifications.py:39
|
||||
#: ops/notifications.py:40
|
||||
#, python-brace-format
|
||||
msgid "The terminal is offline: {name}"
|
||||
msgstr "终端已离线: {name}"
|
||||
|
||||
#: ops/notifications.py:45
|
||||
#: ops/notifications.py:46
|
||||
#, python-brace-format
|
||||
msgid "[Disk] Disk used more than {max_threshold}%: => {value} ({name})"
|
||||
msgstr "[Disk] 硬盘使用率超过 {max_threshold}%: => {value} ({name})"
|
||||
|
||||
#: ops/notifications.py:52
|
||||
#: ops/notifications.py:53
|
||||
#, python-brace-format
|
||||
msgid "[Memory] Memory used more than {max_threshold}%: => {value} ({name})"
|
||||
msgstr "[Memory] 内存使用率超过 {max_threshold}%: => {value} ({name})"
|
||||
|
||||
#: ops/notifications.py:59
|
||||
#: ops/notifications.py:60
|
||||
#, python-brace-format
|
||||
msgid "[CPU] CPU load more than {max_threshold}: => {value} ({name})"
|
||||
msgstr "[CPU] CPU 使用率超过 {max_threshold}: => {value} ({name})"
|
||||
|
@ -2382,7 +2390,7 @@ msgstr "组织存在资源 ({}) 不能被删除"
|
|||
#: orgs/models.py:432 orgs/serializers.py:106
|
||||
#: tickets/serializers/ticket/ticket.py:77
|
||||
msgid "Organization"
|
||||
msgstr "组织审计员"
|
||||
msgstr "组织"
|
||||
|
||||
#: orgs/models.py:17 users/const.py:12
|
||||
msgid "Organization administrator"
|
||||
|
@ -2396,7 +2404,7 @@ msgstr "组织审计员"
|
|||
msgid "GLOBAL"
|
||||
msgstr "全局组织"
|
||||
|
||||
#: orgs/models.py:434 users/models/user.py:614 users/serializers/user.py:36
|
||||
#: orgs/models.py:434 users/models/user.py:614 users/serializers/user.py:37
|
||||
#: users/templates/users/_select_user_modal.html:15
|
||||
msgid "Role"
|
||||
msgstr "角色"
|
||||
|
@ -2483,15 +2491,15 @@ msgstr "来自工单"
|
|||
#: perms/serializers/application/permission.py:18
|
||||
#: perms/serializers/application/permission.py:38
|
||||
#: perms/serializers/asset/permission.py:42
|
||||
#: perms/serializers/asset/permission.py:68 users/serializers/user.py:76
|
||||
#: perms/serializers/asset/permission.py:68 users/serializers/user.py:78
|
||||
msgid "Is valid"
|
||||
msgstr "账户是否有效"
|
||||
|
||||
#: perms/serializers/application/permission.py:19
|
||||
#: perms/serializers/application/permission.py:37
|
||||
#: perms/serializers/asset/permission.py:43
|
||||
#: perms/serializers/asset/permission.py:67 users/serializers/user.py:28
|
||||
#: users/serializers/user.py:77
|
||||
#: perms/serializers/asset/permission.py:67 users/serializers/user.py:29
|
||||
#: users/serializers/user.py:79
|
||||
msgid "Is expired"
|
||||
msgstr "是否过期"
|
||||
|
||||
|
@ -2550,6 +2558,18 @@ msgstr "测试成功"
|
|||
msgid "Test mail sent to {}, please check"
|
||||
msgstr "邮件已经发送{}, 请检查"
|
||||
|
||||
#: settings/api/ldap.py:152
|
||||
msgid "Synchronization start, please wait."
|
||||
msgstr "同步开始,请稍等"
|
||||
|
||||
#: settings/api/ldap.py:156
|
||||
msgid "Synchronization is running, please wait."
|
||||
msgstr "同步正在运行,请稍等"
|
||||
|
||||
#: settings/api/ldap.py:161
|
||||
msgid "Synchronization error: {}"
|
||||
msgstr "同步错误: {}"
|
||||
|
||||
#: settings/api/ldap.py:194
|
||||
msgid "Get ldap users is None"
|
||||
msgstr "获取 LDAP 用户为 None"
|
||||
|
@ -2563,7 +2583,7 @@ msgstr "成功导入 {} 个用户 ( 组织: {} )"
|
|||
msgid "Welcome to the JumpServer open source Bastion Host"
|
||||
msgstr "欢迎使用JumpServer开源堡垒机"
|
||||
|
||||
#: settings/models.py:123 users/templates/users/reset_password.html:29
|
||||
#: settings/models.py:155 users/templates/users/reset_password.html:29
|
||||
msgid "Setting"
|
||||
msgstr "设置"
|
||||
|
||||
|
@ -2612,22 +2632,26 @@ msgid "Server url"
|
|||
msgstr "服务端地址"
|
||||
|
||||
#: settings/serializers/auth/cas.py:13
|
||||
msgid "Proxy server url"
|
||||
msgstr "代理服务地址"
|
||||
|
||||
#: settings/serializers/auth/cas.py:14
|
||||
msgid "Logout completely"
|
||||
msgstr "同步注销"
|
||||
|
||||
#: settings/serializers/auth/cas.py:15
|
||||
#: settings/serializers/auth/cas.py:16
|
||||
msgid "Username attr"
|
||||
msgstr "用户名属性"
|
||||
|
||||
#: settings/serializers/auth/cas.py:16
|
||||
#: settings/serializers/auth/cas.py:17
|
||||
msgid "Enable attributes map"
|
||||
msgstr "启用属性映射"
|
||||
|
||||
#: settings/serializers/auth/cas.py:17
|
||||
#: settings/serializers/auth/cas.py:18
|
||||
msgid "Rename attr"
|
||||
msgstr "映射属性"
|
||||
|
||||
#: settings/serializers/auth/cas.py:18
|
||||
#: settings/serializers/auth/cas.py:19
|
||||
msgid "Create user if not"
|
||||
msgstr "创建用户(如果不存在)"
|
||||
|
||||
|
@ -2686,15 +2710,15 @@ msgstr ""
|
|||
msgid "Periodic display"
|
||||
msgstr "定时执行"
|
||||
|
||||
#: settings/serializers/auth/ldap.py:66
|
||||
#: settings/serializers/auth/ldap.py:68
|
||||
msgid "Connect timeout"
|
||||
msgstr "连接超时时间"
|
||||
|
||||
#: settings/serializers/auth/ldap.py:67
|
||||
#: settings/serializers/auth/ldap.py:70
|
||||
msgid "Search paged size"
|
||||
msgstr "搜索分页数量"
|
||||
|
||||
#: settings/serializers/auth/ldap.py:69
|
||||
#: settings/serializers/auth/ldap.py:72
|
||||
msgid "Enable LDAP auth"
|
||||
msgstr "启用 LDAP 认证"
|
||||
|
||||
|
@ -2829,7 +2853,7 @@ msgstr "其它系统可以使用 SSO Token 对接 JumpServer, 免去登录的过
|
|||
msgid "SSO auth key TTL"
|
||||
msgstr "Token 有效期"
|
||||
|
||||
#: settings/serializers/auth/sso.py:16 settings/serializers/security.py:72
|
||||
#: settings/serializers/auth/sso.py:16 settings/serializers/security.py:74
|
||||
msgid "Unit: second"
|
||||
msgstr "单位: 秒"
|
||||
|
||||
|
@ -2869,29 +2893,29 @@ msgstr "全局组织名"
|
|||
msgid "The name of global organization to display"
|
||||
msgstr "全局组织的显示名称,默认为 全局组织"
|
||||
|
||||
#: settings/serializers/cleaning.py:9
|
||||
#: settings/serializers/cleaning.py:10
|
||||
msgid "Login log keep days"
|
||||
msgstr "登录日志"
|
||||
|
||||
#: settings/serializers/cleaning.py:9 settings/serializers/cleaning.py:12
|
||||
#: settings/serializers/cleaning.py:15 settings/serializers/cleaning.py:18
|
||||
#: settings/serializers/cleaning.py:21
|
||||
#: settings/serializers/cleaning.py:10 settings/serializers/cleaning.py:14
|
||||
#: settings/serializers/cleaning.py:18 settings/serializers/cleaning.py:22
|
||||
#: settings/serializers/cleaning.py:26
|
||||
msgid "Unit: day"
|
||||
msgstr "单位: 天"
|
||||
|
||||
#: settings/serializers/cleaning.py:12
|
||||
#: settings/serializers/cleaning.py:14
|
||||
msgid "Task log keep days"
|
||||
msgstr "任务日志"
|
||||
|
||||
#: settings/serializers/cleaning.py:15
|
||||
#: settings/serializers/cleaning.py:18
|
||||
msgid "Operate log keep days"
|
||||
msgstr "操作日志"
|
||||
|
||||
#: settings/serializers/cleaning.py:18
|
||||
#: settings/serializers/cleaning.py:22
|
||||
msgid "FTP log keep days"
|
||||
msgstr "上传下载"
|
||||
|
||||
#: settings/serializers/cleaning.py:21
|
||||
#: settings/serializers/cleaning.py:26
|
||||
msgid "Cloud sync record keep days"
|
||||
msgstr "云同步记录"
|
||||
|
||||
|
@ -2998,31 +3022,27 @@ msgstr "启用工单系统"
|
|||
msgid "OTP issuer name"
|
||||
msgstr "OTP 扫描后的名称"
|
||||
|
||||
#: settings/serializers/other.py:15
|
||||
#: settings/serializers/other.py:17
|
||||
msgid "OTP valid window"
|
||||
msgstr "OTP 延迟有效次数"
|
||||
|
||||
#: settings/serializers/other.py:17
|
||||
msgid "Enable period task"
|
||||
msgstr "启用周期任务"
|
||||
|
||||
#: settings/serializers/other.py:20
|
||||
#: settings/serializers/other.py:22
|
||||
msgid "CMD"
|
||||
msgstr ""
|
||||
|
||||
#: settings/serializers/other.py:21
|
||||
#: settings/serializers/other.py:23
|
||||
msgid "PowerShell"
|
||||
msgstr ""
|
||||
|
||||
#: settings/serializers/other.py:23
|
||||
#: settings/serializers/other.py:25
|
||||
msgid "Shell (Windows)"
|
||||
msgstr "Shell(Windows 资产)"
|
||||
|
||||
#: settings/serializers/other.py:24
|
||||
#: settings/serializers/other.py:26
|
||||
msgid "The shell type used when Windows assets perform ansible tasks"
|
||||
msgstr "windows 资产执行 Ansible 任务时,使用的 Shell 类型。"
|
||||
|
||||
#: settings/serializers/other.py:28
|
||||
#: settings/serializers/other.py:30
|
||||
msgid "Perm single to ungroup node"
|
||||
msgstr "直接授权资产放在未分组节点"
|
||||
|
||||
|
@ -3120,77 +3140,77 @@ msgstr "开启后,如果系统中不存在该用户,CAS、OIDC 登录将会
|
|||
msgid "Only from source login"
|
||||
msgstr "仅从用户来源登录"
|
||||
|
||||
#: settings/serializers/security.py:72
|
||||
#: settings/serializers/security.py:74
|
||||
msgid "MFA verify TTL"
|
||||
msgstr "MFA 校验有效期"
|
||||
|
||||
#: settings/serializers/security.py:75
|
||||
#: settings/serializers/security.py:78
|
||||
msgid "Enable Login captcha"
|
||||
msgstr "启用登录验证码"
|
||||
|
||||
#: settings/serializers/security.py:81
|
||||
#: settings/serializers/security.py:84
|
||||
msgid "Enable terminal register"
|
||||
msgstr "终端注册"
|
||||
|
||||
#: settings/serializers/security.py:82
|
||||
#: settings/serializers/security.py:85
|
||||
msgid ""
|
||||
"Allow terminal register, after all terminal setup, you should disable this "
|
||||
"for security"
|
||||
msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭"
|
||||
|
||||
#: settings/serializers/security.py:85
|
||||
#: settings/serializers/security.py:88
|
||||
msgid "Replay watermark"
|
||||
msgstr "录像水印"
|
||||
|
||||
#: settings/serializers/security.py:86
|
||||
#: settings/serializers/security.py:89
|
||||
msgid "Enabled, the session replay contains watermark information"
|
||||
msgstr "启用后,会话录像将包含水印信息"
|
||||
|
||||
#: settings/serializers/security.py:90
|
||||
#: settings/serializers/security.py:93
|
||||
msgid "Connection max idle time"
|
||||
msgstr "连接最大空闲时间"
|
||||
|
||||
#: settings/serializers/security.py:91
|
||||
#: settings/serializers/security.py:94
|
||||
msgid "If idle time more than it, disconnect connection Unit: minute"
|
||||
msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)"
|
||||
|
||||
#: settings/serializers/security.py:94
|
||||
#: settings/serializers/security.py:97
|
||||
msgid "Remember manual auth"
|
||||
msgstr "保存手动输入密码"
|
||||
|
||||
#: settings/serializers/security.py:97
|
||||
#: settings/serializers/security.py:100
|
||||
msgid "Enable change auth secure mode"
|
||||
msgstr "启用改密安全模式"
|
||||
|
||||
#: settings/serializers/security.py:100
|
||||
#: settings/serializers/security.py:103
|
||||
msgid "Insecure command alert"
|
||||
msgstr "危险命令告警"
|
||||
|
||||
#: settings/serializers/security.py:103
|
||||
#: settings/serializers/security.py:106
|
||||
msgid "Email recipient"
|
||||
msgstr "邮件收件人"
|
||||
|
||||
#: settings/serializers/security.py:104
|
||||
#: settings/serializers/security.py:107
|
||||
msgid "Multiple user using , split"
|
||||
msgstr "多个用户,使用 , 分割"
|
||||
|
||||
#: settings/serializers/security.py:107
|
||||
#: settings/serializers/security.py:110
|
||||
msgid "Batch command execution"
|
||||
msgstr "批量命令执行"
|
||||
|
||||
#: settings/serializers/security.py:108
|
||||
#: settings/serializers/security.py:111
|
||||
msgid "Allow user run batch command or not using ansible"
|
||||
msgstr "是否允许用户使用 ansible 执行批量命令"
|
||||
|
||||
#: settings/serializers/security.py:111
|
||||
#: settings/serializers/security.py:114
|
||||
msgid "Session share"
|
||||
msgstr "会话分享"
|
||||
|
||||
#: settings/serializers/security.py:112
|
||||
#: settings/serializers/security.py:115
|
||||
msgid "Enabled, Allows user active session to be shared with other users"
|
||||
msgstr "开启后允许用户分享已连接的资产会话给它人,协同工作"
|
||||
|
||||
#: settings/serializers/security.py:116
|
||||
#: settings/serializers/security.py:119
|
||||
msgid "Enabled, please go to the user detail add approver"
|
||||
msgstr "启用后, 请在用户详情中添加审批人"
|
||||
|
||||
|
@ -3542,7 +3562,7 @@ msgstr "数据库应用"
|
|||
msgid "Perms"
|
||||
msgstr "权限管理"
|
||||
|
||||
#: templates/_nav.html:97 terminal/notifications.py:19
|
||||
#: templates/_nav.html:97 terminal/notifications.py:20
|
||||
msgid "Sessions"
|
||||
msgstr "会话管理"
|
||||
|
||||
|
@ -4051,11 +4071,11 @@ msgstr "命令存储"
|
|||
msgid "Replay storage"
|
||||
msgstr "录像存储"
|
||||
|
||||
#: terminal/notifications.py:65
|
||||
#: terminal/notifications.py:66
|
||||
msgid "Danger command alert"
|
||||
msgstr "危险命令告警"
|
||||
|
||||
#: terminal/notifications.py:78
|
||||
#: terminal/notifications.py:82
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -4074,7 +4094,7 @@ msgstr ""
|
|||
"会话: %(session_detail_url)s?oid=%(oid)s\n"
|
||||
" "
|
||||
|
||||
#: terminal/notifications.py:106
|
||||
#: terminal/notifications.py:113
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -4104,11 +4124,11 @@ msgstr ""
|
|||
" <br>\n"
|
||||
" "
|
||||
|
||||
#: terminal/notifications.py:135
|
||||
#: terminal/notifications.py:142
|
||||
msgid "Batch danger command alert"
|
||||
msgstr "批量危险命令告警"
|
||||
|
||||
#: terminal/notifications.py:146
|
||||
#: terminal/notifications.py:153
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -4140,7 +4160,7 @@ msgstr ""
|
|||
"<br>\n"
|
||||
" "
|
||||
|
||||
#: terminal/notifications.py:173
|
||||
#: terminal/notifications.py:180
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -4178,6 +4198,14 @@ msgstr "是否可重放"
|
|||
msgid "Can join"
|
||||
msgstr "是否可加入"
|
||||
|
||||
#: terminal/serializers/session.py:40
|
||||
msgid "Can terminate"
|
||||
msgstr "是否可中断"
|
||||
|
||||
#: terminal/serializers/session.py:50
|
||||
msgid "Command amount"
|
||||
msgstr "命令数量"
|
||||
|
||||
#: terminal/serializers/storage.py:21
|
||||
msgid "Endpoint invalid: remove path `{}`"
|
||||
msgstr "端点无效: 移除路径 `{}`"
|
||||
|
@ -4311,42 +4339,42 @@ msgstr "自定义用户"
|
|||
msgid "Ticket already closed"
|
||||
msgstr "工单已经关闭"
|
||||
|
||||
#: tickets/handler/apply_application.py:51
|
||||
#: tickets/handler/apply_application.py:52
|
||||
msgid "Applied category"
|
||||
msgstr "申请的类别"
|
||||
|
||||
#: tickets/handler/apply_application.py:52
|
||||
#: tickets/handler/apply_application.py:53
|
||||
msgid "Applied type"
|
||||
msgstr "申请的类型"
|
||||
|
||||
#: tickets/handler/apply_application.py:53
|
||||
#: tickets/handler/apply_application.py:54
|
||||
msgid "Applied application group"
|
||||
msgstr "申请的应用组"
|
||||
|
||||
#: tickets/handler/apply_application.py:54 tickets/handler/apply_asset.py:47
|
||||
#: tickets/handler/apply_application.py:55 tickets/handler/apply_asset.py:48
|
||||
msgid "Applied system user group"
|
||||
msgstr "申请的系统用户组"
|
||||
|
||||
#: tickets/handler/apply_application.py:55 tickets/handler/apply_asset.py:49
|
||||
#: tickets/handler/apply_application.py:56 tickets/handler/apply_asset.py:50
|
||||
msgid "Applied date start"
|
||||
msgstr "申请的开始日期"
|
||||
|
||||
#: tickets/handler/apply_application.py:56 tickets/handler/apply_asset.py:50
|
||||
#: tickets/handler/apply_application.py:57 tickets/handler/apply_asset.py:51
|
||||
msgid "Applied date expired"
|
||||
msgstr "申请的失效日期"
|
||||
|
||||
#: tickets/handler/apply_application.py:78 tickets/handler/apply_asset.py:71
|
||||
#: tickets/handler/apply_application.py:79 tickets/handler/apply_asset.py:72
|
||||
msgid ""
|
||||
"Created by the ticket, ticket title: {}, ticket applicant: {}, ticket "
|
||||
"processor: {}, ticket ID: {}"
|
||||
msgstr ""
|
||||
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}"
|
||||
|
||||
#: tickets/handler/apply_asset.py:46
|
||||
#: tickets/handler/apply_asset.py:47
|
||||
msgid "Applied hostname group"
|
||||
msgstr "申请的主机名组"
|
||||
|
||||
#: tickets/handler/apply_asset.py:48
|
||||
#: tickets/handler/apply_asset.py:49
|
||||
msgid "Applied actions"
|
||||
msgstr "申请的动作"
|
||||
|
||||
|
@ -4499,15 +4527,15 @@ msgstr "流程"
|
|||
msgid "TicketFlow"
|
||||
msgstr "工单流程"
|
||||
|
||||
#: tickets/notifications.py:44 tickets/notifications.py:56
|
||||
#: tickets/notifications.py:57
|
||||
msgid "click here to review"
|
||||
msgstr "点击查看"
|
||||
|
||||
#: tickets/notifications.py:74
|
||||
#: tickets/notifications.py:75
|
||||
msgid "Your has a new ticket, applicant - {}"
|
||||
msgstr "你有一个新的工单, 申请人 - {}"
|
||||
|
||||
#: tickets/notifications.py:87
|
||||
#: tickets/notifications.py:88
|
||||
msgid "Your ticket has been processed, processor - {}"
|
||||
msgstr "你的工单已被处理, 处理人 - {}"
|
||||
|
||||
|
@ -4538,6 +4566,11 @@ msgstr "批准的系统用户名称"
|
|||
msgid "Permission named `{}` already exists"
|
||||
msgstr "授权名称 `{}` 已存在"
|
||||
|
||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:75
|
||||
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:67
|
||||
msgid "The expiration date should be greater than the start date"
|
||||
msgstr "过期时间要大于开始时间"
|
||||
|
||||
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:20
|
||||
msgid "Apply assets"
|
||||
msgstr "申请资产"
|
||||
|
@ -4774,13 +4807,13 @@ msgstr "管理员"
|
|||
msgid "Administrator is the super user of system"
|
||||
msgstr "Administrator是初始的超级管理员"
|
||||
|
||||
#: users/notifications.py:12 users/notifications.py:45
|
||||
#: users/notifications.py:17 users/notifications.py:50
|
||||
#: users/templates/users/reset_password.html:5
|
||||
#: users/templates/users/reset_password.html:6
|
||||
msgid "Reset password"
|
||||
msgstr "重置密码"
|
||||
|
||||
#: users/notifications.py:13
|
||||
#: users/notifications.py:18
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -4820,7 +4853,7 @@ msgstr ""
|
|||
"%(login_url)s\n"
|
||||
"\n"
|
||||
|
||||
#: users/notifications.py:46
|
||||
#: users/notifications.py:51
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -4864,12 +4897,12 @@ msgstr ""
|
|||
" <br>\n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:85 users/notifications.py:119
|
||||
#: users/notifications.py:90 users/notifications.py:122
|
||||
#: users/views/profile/reset.py:127
|
||||
msgid "Reset password success"
|
||||
msgstr "重置密码成功"
|
||||
|
||||
#: users/notifications.py:86
|
||||
#: users/notifications.py:91
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -4889,10 +4922,9 @@ msgid ""
|
|||
"\n"
|
||||
"\n"
|
||||
"IP Address: %(ip_address)s\n"
|
||||
"<br>\n"
|
||||
"<br>\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"Browser: %(browser)s\n"
|
||||
"<br>\n"
|
||||
" \n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
@ -4911,14 +4943,12 @@ msgstr ""
|
|||
"\n"
|
||||
"\n"
|
||||
"IP 地址: %(ip_address)s\n"
|
||||
"<br>\n"
|
||||
"<br>\n"
|
||||
"\n"
|
||||
"浏览器: %(browser)s\n"
|
||||
"<br>\n"
|
||||
" \n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:120
|
||||
#: users/notifications.py:123
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -4981,11 +5011,11 @@ msgstr ""
|
|||
" \n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:163 users/notifications.py:199
|
||||
#: users/notifications.py:166 users/notifications.py:202
|
||||
msgid "Security notice"
|
||||
msgstr "安全通知"
|
||||
|
||||
#: users/notifications.py:164
|
||||
#: users/notifications.py:167
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -5029,7 +5059,7 @@ msgstr ""
|
|||
"\n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:200
|
||||
#: users/notifications.py:203
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -5078,11 +5108,11 @@ msgstr ""
|
|||
" <br>\n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:237 users/notifications.py:256
|
||||
#: users/notifications.py:240 users/notifications.py:259
|
||||
msgid "Expiration notice"
|
||||
msgstr "过期通知"
|
||||
|
||||
#: users/notifications.py:238
|
||||
#: users/notifications.py:241
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -5103,7 +5133,7 @@ msgstr ""
|
|||
"为了不影响您正常工作,请联系管理员确认。\n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:257
|
||||
#: users/notifications.py:260
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -5125,11 +5155,11 @@ msgstr ""
|
|||
" <br>\n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:277 users/notifications.py:298
|
||||
#: users/notifications.py:280 users/notifications.py:301
|
||||
msgid "SSH Key Reset"
|
||||
msgstr "重置SSH密钥"
|
||||
|
||||
#: users/notifications.py:278
|
||||
#: users/notifications.py:281
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -5154,7 +5184,7 @@ msgstr ""
|
|||
"\n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:299
|
||||
#: users/notifications.py:302
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -5179,11 +5209,11 @@ msgstr ""
|
|||
" <br>\n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:321 users/notifications.py:341
|
||||
#: users/notifications.py:324 users/notifications.py:344
|
||||
msgid "MFA Reset"
|
||||
msgstr "重置 MFA"
|
||||
|
||||
#: users/notifications.py:322
|
||||
#: users/notifications.py:325
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -5208,7 +5238,7 @@ msgstr ""
|
|||
"\n"
|
||||
" "
|
||||
|
||||
#: users/notifications.py:342
|
||||
#: users/notifications.py:345
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -5237,7 +5267,7 @@ msgstr ""
|
|||
msgid "The old password is incorrect"
|
||||
msgstr "旧密码错误"
|
||||
|
||||
#: users/serializers/profile.py:36 users/serializers/user.py:137
|
||||
#: users/serializers/profile.py:36 users/serializers/user.py:140
|
||||
msgid "Password does not match security rules"
|
||||
msgstr "密码不满足安全规则"
|
||||
|
||||
|
@ -5249,81 +5279,85 @@ msgstr "新密码不能是最近 {} 次的密码"
|
|||
msgid "The newly set password is inconsistent"
|
||||
msgstr "两次密码不一致"
|
||||
|
||||
#: users/serializers/profile.py:121 users/serializers/user.py:75
|
||||
#: users/serializers/profile.py:121 users/serializers/user.py:77
|
||||
msgid "Is first login"
|
||||
msgstr "首次登录"
|
||||
|
||||
#: users/serializers/user.py:22
|
||||
#: users/serializers/user.py:23
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:32
|
||||
#: xpack/plugins/change_auth_plan/serializers/base.py:24
|
||||
msgid "Password strategy"
|
||||
msgstr "密码策略"
|
||||
|
||||
#: users/serializers/user.py:24
|
||||
#: users/serializers/user.py:25
|
||||
msgid "MFA enabled"
|
||||
msgstr "是否开启多因子认证"
|
||||
|
||||
#: users/serializers/user.py:25
|
||||
#: users/serializers/user.py:26
|
||||
msgid "MFA force enabled"
|
||||
msgstr "强制启用多因子认证"
|
||||
|
||||
#: users/serializers/user.py:26
|
||||
#: users/serializers/user.py:27
|
||||
msgid "MFA level display"
|
||||
msgstr "多因子认证等级名称"
|
||||
|
||||
#: users/serializers/user.py:27
|
||||
#: users/serializers/user.py:28
|
||||
msgid "Login blocked"
|
||||
msgstr "登录被阻塞"
|
||||
|
||||
#: users/serializers/user.py:29
|
||||
#: users/serializers/user.py:30
|
||||
msgid "Can update"
|
||||
msgstr "是否可更新"
|
||||
|
||||
#: users/serializers/user.py:30
|
||||
#: users/serializers/user.py:31
|
||||
msgid "Can delete"
|
||||
msgstr "是否可删除"
|
||||
|
||||
#: users/serializers/user.py:33 users/serializers/user.py:82
|
||||
#: users/serializers/user.py:32
|
||||
msgid "Can public key authentication"
|
||||
msgstr "能否公钥认证"
|
||||
|
||||
#: users/serializers/user.py:34 users/serializers/user.py:84
|
||||
msgid "Organization role name"
|
||||
msgstr "组织角色名称"
|
||||
|
||||
#: users/serializers/user.py:78
|
||||
#: users/serializers/user.py:80
|
||||
msgid "Avatar url"
|
||||
msgstr "头像路径"
|
||||
|
||||
#: users/serializers/user.py:80
|
||||
#: users/serializers/user.py:82
|
||||
msgid "Groups name"
|
||||
msgstr "用户组名"
|
||||
|
||||
#: users/serializers/user.py:81
|
||||
#: users/serializers/user.py:83
|
||||
msgid "Source name"
|
||||
msgstr "用户来源名"
|
||||
|
||||
#: users/serializers/user.py:83
|
||||
#: users/serializers/user.py:85
|
||||
msgid "Super role name"
|
||||
msgstr "超级角色名称"
|
||||
|
||||
#: users/serializers/user.py:84
|
||||
#: users/serializers/user.py:86
|
||||
msgid "Total role name"
|
||||
msgstr "汇总角色名称"
|
||||
|
||||
#: users/serializers/user.py:86
|
||||
#: users/serializers/user.py:88
|
||||
msgid "Is wecom bound"
|
||||
msgstr "是否绑定了企业微信"
|
||||
|
||||
#: users/serializers/user.py:87
|
||||
#: users/serializers/user.py:89
|
||||
msgid "Is dingtalk bound"
|
||||
msgstr "是否绑定了钉钉"
|
||||
|
||||
#: users/serializers/user.py:88
|
||||
#: users/serializers/user.py:90
|
||||
msgid "Is feishu bound"
|
||||
msgstr "是否绑定了飞书"
|
||||
|
||||
#: users/serializers/user.py:111
|
||||
#: users/serializers/user.py:114
|
||||
msgid "Role limit to {}"
|
||||
msgstr "角色只能为 {}"
|
||||
|
||||
#: users/serializers/user.py:231
|
||||
#: users/serializers/user.py:234
|
||||
msgid "name not unique"
|
||||
msgstr "名称重复"
|
||||
|
||||
|
@ -5682,8 +5716,8 @@ msgid "Empty and append SSH KEY"
|
|||
msgstr "清空所有并添加"
|
||||
|
||||
#: xpack/plugins/change_auth_plan/models/asset.py:32
|
||||
msgid "Empty pre add and append SSH KEY"
|
||||
msgstr "清空上次并添加"
|
||||
msgid "Replace (The key generated by JumpServer) "
|
||||
msgstr "替换 (由 JumpServer 生成的密钥)"
|
||||
|
||||
#: xpack/plugins/change_auth_plan/models/asset.py:50
|
||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:34
|
||||
|
|
|
@ -164,7 +164,6 @@ class SystemMessage(Message):
|
|||
|
||||
self.send_msg(users, receive_backends)
|
||||
|
||||
|
||||
@classmethod
|
||||
def post_insert_to_db(cls, subscription: SystemMsgSubscription):
|
||||
pass
|
||||
|
|
|
@ -18,8 +18,9 @@ class ServerPerformanceMessage(SystemMessage):
|
|||
self._msg = msg
|
||||
|
||||
def get_common_msg(self):
|
||||
subject = self._msg[:80]
|
||||
return {
|
||||
'subject': self._msg[:80],
|
||||
'subject': subject.replace('<br>', '; '),
|
||||
'message': self._msg
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ from common.decorator import on_transaction_commit
|
|||
from common.signals import django_ready
|
||||
from common.utils import get_logger
|
||||
from common.utils.connection import RedisPubSub
|
||||
from assets.models import CommandFilterRule
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -91,17 +92,18 @@ def on_org_delete(sender, instance, **kwargs):
|
|||
root_node.delete()
|
||||
|
||||
|
||||
def _remove_users(model, users, org):
|
||||
def _remove_users(model, users, org, user_field_name='users'):
|
||||
with tmp_to_org(org):
|
||||
if not isinstance(users, (tuple, list, set)):
|
||||
users = (users, )
|
||||
|
||||
m2m_model = model.users.through
|
||||
reverse = model.users.reverse
|
||||
user_field = getattr(model, user_field_name)
|
||||
m2m_model = user_field.through
|
||||
reverse = user_field.reverse
|
||||
if reverse:
|
||||
m2m_field_name = model.users.field.m2m_reverse_field_name()
|
||||
m2m_field_name = user_field.field.m2m_reverse_field_name()
|
||||
else:
|
||||
m2m_field_name = model.users.field.m2m_field_name()
|
||||
m2m_field_name = user_field.field.m2m_field_name()
|
||||
relations = m2m_model.objects.filter(**{
|
||||
'user__in': users,
|
||||
f'{m2m_field_name}__org_id': org.id
|
||||
|
@ -149,6 +151,8 @@ def _clear_users_from_org(org, users):
|
|||
for m in models:
|
||||
_remove_users(m, users, org)
|
||||
|
||||
_remove_users(CommandFilterRule, users, org, user_field_name='reviewers')
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=OrganizationMember)
|
||||
def on_org_user_changed(action, instance, reverse, pk_set, **kwargs):
|
||||
|
|
|
@ -54,13 +54,24 @@ def get_user_all_asset_perm_ids(user) -> set:
|
|||
|
||||
|
||||
class UserGrantedTreeRefreshController:
|
||||
key_template = 'perms.user.node_tree.builded_orgs.user_id:{user_id}'
|
||||
key_template = 'perms.user.node_tree.built_orgs.user_id:{user_id}'
|
||||
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
self.key = self.key_template.format(user_id=user.id)
|
||||
self.client = self.get_redis_client()
|
||||
|
||||
@classmethod
|
||||
def clean_all_user_tree_built_mark(cls):
|
||||
""" 清除所有用户已构建树的标记 """
|
||||
client = cls.get_redis_client()
|
||||
key_match = cls.key_template.format(user_id='*')
|
||||
keys = client.keys(key_match)
|
||||
with client.pipeline() as p:
|
||||
for key in keys:
|
||||
p.delete(key)
|
||||
p.execute()
|
||||
|
||||
@classmethod
|
||||
def get_redis_client(cls):
|
||||
return cache.client.get_client(write=True)
|
||||
|
@ -69,13 +80,13 @@ class UserGrantedTreeRefreshController:
|
|||
org_ids = self.client.smembers(self.key)
|
||||
return {org_id.decode() for org_id in org_ids}
|
||||
|
||||
def set_all_orgs_as_builed(self):
|
||||
def set_all_orgs_as_built(self):
|
||||
self.client.sadd(self.key, *self.org_ids)
|
||||
|
||||
def have_need_refresh_orgs(self):
|
||||
builded_org_ids = self.client.smembers(self.key)
|
||||
builded_org_ids = {org_id.decode() for org_id in builded_org_ids}
|
||||
have = self.org_ids - builded_org_ids
|
||||
built_org_ids = self.client.smembers(self.key)
|
||||
built_org_ids = {org_id.decode() for org_id in built_org_ids}
|
||||
have = self.org_ids - built_org_ids
|
||||
return have
|
||||
|
||||
def get_need_refresh_orgs_and_fill_up(self):
|
||||
|
@ -85,15 +96,18 @@ class UserGrantedTreeRefreshController:
|
|||
p.smembers(self.key)
|
||||
p.sadd(self.key, *org_ids)
|
||||
ret = p.execute()
|
||||
builded_org_ids = {org_id.decode() for org_id in ret[0]}
|
||||
ids = org_ids - builded_org_ids
|
||||
built_org_ids = {org_id.decode() for org_id in ret[0]}
|
||||
ids = org_ids - built_org_ids
|
||||
orgs = {*Organization.objects.filter(id__in=ids)}
|
||||
logger.info(f'Need rebuild orgs are {orgs}, builed orgs are {ret[0]}, all orgs are {org_ids}')
|
||||
logger.info(
|
||||
f'Need rebuild orgs are {orgs}, built orgs are {ret[0]}, '
|
||||
f'all orgs are {org_ids}'
|
||||
)
|
||||
return orgs
|
||||
|
||||
@classmethod
|
||||
@on_transaction_commit
|
||||
def remove_builed_orgs_from_users(cls, org_ids, user_ids):
|
||||
def remove_built_orgs_from_users(cls, org_ids, user_ids):
|
||||
client = cls.get_redis_client()
|
||||
org_ids = [str(org_id) for org_id in org_ids]
|
||||
|
||||
|
@ -102,11 +116,12 @@ class UserGrantedTreeRefreshController:
|
|||
key = cls.key_template.format(user_id=user_id)
|
||||
p.srem(key, *org_ids)
|
||||
p.execute()
|
||||
logger.info(f'Remove orgs from users builded tree: users:{user_ids} orgs:{org_ids}')
|
||||
logger.info(f'Remove orgs from users built tree: users:{user_ids} '
|
||||
f'orgs:{org_ids}')
|
||||
|
||||
@classmethod
|
||||
def add_need_refresh_orgs_for_users(cls, org_ids, user_ids):
|
||||
cls.remove_builed_orgs_from_users(org_ids, user_ids)
|
||||
cls.remove_built_orgs_from_users(org_ids, user_ids)
|
||||
|
||||
@classmethod
|
||||
@ensure_in_real_or_default_org
|
||||
|
@ -168,7 +183,7 @@ class UserGrantedTreeRefreshController:
|
|||
).values_list('user_id', flat=True)
|
||||
user_ids.update(group_user_ids)
|
||||
|
||||
cls.remove_builed_orgs_from_users(
|
||||
cls.remove_built_orgs_from_users(
|
||||
[current_org.id], user_ids
|
||||
)
|
||||
|
||||
|
@ -193,7 +208,7 @@ class UserGrantedTreeRefreshController:
|
|||
with UserGrantedTreeRebuildLock(user_id=user.id):
|
||||
if force:
|
||||
orgs = self.orgs
|
||||
self.set_all_orgs_as_builed()
|
||||
self.set_all_orgs_as_built()
|
||||
else:
|
||||
orgs = self.get_need_refresh_orgs_and_fill_up()
|
||||
|
||||
|
|
|
@ -149,16 +149,16 @@ class LDAPUserListApi(generics.ListAPIView):
|
|||
sync_util.set_task_status(sync_util.TASK_STATUS_IS_RUNNING)
|
||||
t = threading.Thread(target=sync_ldap_user)
|
||||
t.start()
|
||||
data = {'msg': 'Sync start.'}
|
||||
data = {'msg': _('Synchronization start, please wait.')}
|
||||
return Response(data=data, status=409)
|
||||
# 同步任务正在执行
|
||||
if sync_util.task_is_running:
|
||||
data = {'msg': 'synchronization is running.'}
|
||||
data = {'msg': _('Synchronization is running, please wait.')}
|
||||
return Response(data=data, status=409)
|
||||
# 同步任务执行结束
|
||||
if sync_util.task_is_over:
|
||||
msg = sync_util.get_task_error_msg()
|
||||
data = {'error': 'Synchronization error: {}'.format(msg)}
|
||||
data = {'error': _('Synchronization error: {}'.format(msg))}
|
||||
return Response(data=data, status=400)
|
||||
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
|
|
@ -117,6 +117,10 @@ class Setting(models.Model):
|
|||
# 设置内存值
|
||||
setattr(settings, name, setting.cleaned_value)
|
||||
|
||||
@classmethod
|
||||
def refresh_AUTH_CAS(cls):
|
||||
cls.refresh_authentications('AUTH_CAS')
|
||||
|
||||
@classmethod
|
||||
def refresh_AUTH_LDAP(cls):
|
||||
cls.refresh_authentications('AUTH_LDAP')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
import json
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
|
@ -10,8 +10,9 @@ __all__ = [
|
|||
class CASSettingSerializer(serializers.Serializer):
|
||||
AUTH_CAS = serializers.BooleanField(required=False, label=_('Enable CAS Auth'))
|
||||
CAS_SERVER_URL = serializers.CharField(required=False, max_length=1024, label=_('Server url'))
|
||||
CAS_ROOT_PROXIED_AS = serializers.CharField(required=False, max_length=1024, label=_('Proxy server url'))
|
||||
CAS_LOGOUT_COMPLETELY = serializers.BooleanField(required=False, label=_('Logout completely'))
|
||||
CAS_VERSION = serializers.IntegerField(required=False, label=_('Version'))
|
||||
CAS_VERSION = serializers.IntegerField(required=False, label=_('Version'), min_value=1, max_value=3)
|
||||
CAS_USERNAME_ATTRIBUTE = serializers.CharField(required=False, max_length=1024, label=_('Username attr'))
|
||||
CAS_APPLY_ATTRIBUTES_TO_USER = serializers.BooleanField(required=False, label=_('Enable attributes map'))
|
||||
CAS_RENAME_ATTRIBUTES = serializers.DictField(required=False, label=_('Rename attr'))
|
||||
|
|
|
@ -63,7 +63,10 @@ class LDAPSettingSerializer(serializers.Serializer):
|
|||
AUTH_LDAP_SYNC_CRONTAB = serializers.CharField(
|
||||
required=False, max_length=1024, allow_null=True, label=_('Regularly perform')
|
||||
)
|
||||
AUTH_LDAP_CONNECT_TIMEOUT = serializers.IntegerField(required=False, label=_('Connect timeout'))
|
||||
AUTH_LDAP_CONNECT_TIMEOUT = serializers.IntegerField(
|
||||
min_value=1, max_value=300,
|
||||
required=False, label=_('Connect timeout'),
|
||||
)
|
||||
AUTH_LDAP_SEARCH_PAGED_SIZE = serializers.IntegerField(required=False, label=_('Search paged size'))
|
||||
|
||||
AUTH_LDAP = serializers.BooleanField(required=False, label=_('Enable LDAP auth'))
|
||||
|
|
|
@ -13,5 +13,6 @@ class SSOSettingSerializer(serializers.Serializer):
|
|||
help_text=_("Other service can using SSO token login to JumpServer without password")
|
||||
)
|
||||
AUTH_SSO_AUTHKEY_TTL = serializers.IntegerField(
|
||||
required=False, label=_('SSO auth key TTL'), help_text=_("Unit: second")
|
||||
required=False, label=_('SSO auth key TTL'), help_text=_("Unit: second"),
|
||||
min_value=1, max_value=60*30
|
||||
)
|
||||
|
|
|
@ -6,17 +6,22 @@ __all__ = ['CleaningSerializer']
|
|||
|
||||
class CleaningSerializer(serializers.Serializer):
|
||||
LOGIN_LOG_KEEP_DAYS = serializers.IntegerField(
|
||||
min_value=1, max_value=9999,
|
||||
label=_("Login log keep days"), help_text=_("Unit: day")
|
||||
)
|
||||
TASK_LOG_KEEP_DAYS = serializers.IntegerField(
|
||||
min_value=1, max_value=9999,
|
||||
label=_("Task log keep days"), help_text=_("Unit: day")
|
||||
)
|
||||
OPERATE_LOG_KEEP_DAYS = serializers.IntegerField(
|
||||
min_value=1, max_value=9999,
|
||||
label=_("Operate log keep days"), help_text=_("Unit: day")
|
||||
)
|
||||
FTP_LOG_KEEP_DAYS = serializers.IntegerField(
|
||||
min_value=1, max_value=9999,
|
||||
label=_("FTP log keep days"), help_text=_("Unit: day")
|
||||
)
|
||||
CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS = serializers.IntegerField(
|
||||
min_value=1, max_value=9999,
|
||||
label=_("Cloud sync record keep days"), help_text=_("Unit: day")
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@ __all__ = ['MailTestSerializer', 'EmailSettingSerializer', 'EmailContentSettingS
|
|||
|
||||
class MailTestSerializer(serializers.Serializer):
|
||||
EMAIL_HOST = serializers.CharField(max_length=1024, required=True)
|
||||
EMAIL_PORT = serializers.IntegerField(default=25)
|
||||
EMAIL_PORT = serializers.IntegerField(default=25, min_value=1, max_value=65535)
|
||||
EMAIL_HOST_USER = serializers.CharField(max_length=1024)
|
||||
EMAIL_HOST_PASSWORD = serializers.CharField(required=False, allow_blank=True)
|
||||
EMAIL_FROM = serializers.CharField(required=False, allow_blank=True)
|
||||
|
|
|
@ -12,9 +12,11 @@ class OtherSettingSerializer(serializers.Serializer):
|
|||
OTP_ISSUER_NAME = serializers.CharField(
|
||||
required=False, max_length=1024, label=_('OTP issuer name'),
|
||||
)
|
||||
OTP_VALID_WINDOW = serializers.IntegerField(label=_("OTP valid window"))
|
||||
OTP_VALID_WINDOW = serializers.IntegerField(
|
||||
min_value=1, max_value=10,
|
||||
label=_("OTP valid window")
|
||||
)
|
||||
|
||||
PERIOD_TASK_ENABLED = serializers.BooleanField(required=False, label=_("Enable period task"))
|
||||
WINDOWS_SSH_DEFAULT_SHELL = serializers.ChoiceField(
|
||||
choices=[
|
||||
('cmd', _("CMD")),
|
||||
|
@ -28,4 +30,8 @@ class OtherSettingSerializer(serializers.Serializer):
|
|||
required=False, label=_("Perm single to ungroup node")
|
||||
)
|
||||
|
||||
# 准备废弃
|
||||
# PERIOD_TASK_ENABLED = serializers.BooleanField(
|
||||
# required=False, label=_("Enable period task")
|
||||
# )
|
||||
|
||||
|
|
|
@ -69,7 +69,10 @@ class SecurityAuthSerializer(serializers.Serializer):
|
|||
required=False, default=False, label=_("Only from source login"),
|
||||
help_text=_("If enable, CAS、OIDC auth will be failed, if user not exist yet")
|
||||
)
|
||||
SECURITY_MFA_VERIFY_TTL = serializers.IntegerField(label=_("MFA verify TTL"), help_text=_("Unit: second"))
|
||||
SECURITY_MFA_VERIFY_TTL = serializers.IntegerField(
|
||||
min_value=5, max_value=60*30,
|
||||
label=_("MFA verify TTL"), help_text=_("Unit: second"),
|
||||
)
|
||||
SECURITY_LOGIN_CAPTCHA_ENABLED = serializers.BooleanField(
|
||||
required=False, default=True,
|
||||
label=_("Enable Login captcha")
|
||||
|
|
|
@ -33,8 +33,17 @@ setting_pub_sub = SettingSubPub()
|
|||
@receiver(post_save, sender=Setting)
|
||||
@on_transaction_commit
|
||||
def refresh_settings_on_changed(sender, instance=None, **kwargs):
|
||||
if instance:
|
||||
setting_pub_sub.publish(instance.name)
|
||||
if not instance:
|
||||
return
|
||||
|
||||
setting_pub_sub.publish(instance.name)
|
||||
|
||||
# 配置变化: PERM_SINGLE_ASSET_TO_UNGROUP_NODE
|
||||
if instance.name == 'PERM_SINGLE_ASSET_TO_UNGROUP_NODE':
|
||||
# 清除所有用户授权树已构建的标记,下次访问重新生成
|
||||
logger.debug('Clean ALL User perm tree built mark')
|
||||
from perms.utils.asset import UserGrantedTreeRefreshController
|
||||
UserGrantedTreeRefreshController.clean_all_user_tree_built_mark()
|
||||
|
||||
|
||||
@receiver(django_ready)
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<div class="group">
|
||||
<h2>JumpServer Clients</h2>
|
||||
<ul>
|
||||
<li><a href="/download/jumpserver-client.msi.zip">jumpserver-client-windows.msi.zip</a></li>
|
||||
<li><a href="/download/jumpserver-client.dmg">jumpserver-client-macos.dmg</a></li>
|
||||
<li><a href="/download/JumpServer-Client-Installer.msi">jumpserver-client-windows.msi</a></li>
|
||||
<li><a href="/download/JumpServer-Client-Installer.dmg">jumpserver-client-macos.dmg</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class CommandStore():
|
|||
hosts = config.get("HOSTS")
|
||||
kwargs = config.get("OTHER", {})
|
||||
self.index = config.get("INDEX") or 'jumpserver'
|
||||
self.doc_type = config.get("DOC_TYPE") or 'command_store'
|
||||
self.doc_type = config.get("DOC_TYPE") or '_doc'
|
||||
self.exact_fields = {}
|
||||
self.match_fields = {}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ from notifications.notifications import SystemMessage
|
|||
from terminal.models import Session, Command
|
||||
from notifications.models import SystemMsgSubscription
|
||||
from notifications.backends import BACKEND
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from common.utils import lazyproperty
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
@ -69,7 +70,10 @@ class CommandAlertMessage(CommandAlertMixin, SystemMessage):
|
|||
|
||||
def get_text_msg(self) -> dict:
|
||||
command = self.command
|
||||
session = Session.objects.get(id=command['session'])
|
||||
|
||||
with tmp_to_root_org():
|
||||
session = Session.objects.get(id=command['session'])
|
||||
|
||||
session_detail_url = reverse(
|
||||
'api-terminal:session-detail', kwargs={'pk': command['session']},
|
||||
external=True, api_to_ui=True
|
||||
|
@ -97,7 +101,10 @@ Session: %(session_detail_url)s?oid=%(oid)s
|
|||
|
||||
def get_html_msg(self) -> dict:
|
||||
command = self.command
|
||||
session = Session.objects.get(id=command['session'])
|
||||
|
||||
with tmp_to_root_org():
|
||||
session = Session.objects.get(id=command['session'])
|
||||
|
||||
session_detail_url = reverse(
|
||||
'api-terminal:session-detail', kwargs={'pk': command['session']},
|
||||
external=True, api_to_ui=True
|
||||
|
|
|
@ -37,6 +37,7 @@ class SessionSerializer(BulkOrgResourceModelSerializer):
|
|||
'can_join': {'label': _('Can join')},
|
||||
'terminal': {'label': _('Terminal')},
|
||||
'is_finished': {'label': _('Is finished')},
|
||||
'can_terminate': {'label': _('Can terminate')},
|
||||
}
|
||||
|
||||
|
||||
|
@ -45,6 +46,9 @@ class SessionDisplaySerializer(SessionSerializer):
|
|||
|
||||
class Meta(SessionSerializer.Meta):
|
||||
fields = SessionSerializer.Meta.fields + ['command_amount']
|
||||
extra_kwargs = {
|
||||
'command_amount': {'label': _('Command amount')},
|
||||
}
|
||||
|
||||
|
||||
class ReplaySerializer(serializers.Serializer):
|
||||
|
|
|
@ -23,9 +23,16 @@ class BaseTerminal(object):
|
|||
name = f'[{suffix_name}]-{hostname}'
|
||||
self.name = name
|
||||
self.interval = 30
|
||||
self.remote_addr = socket.gethostbyname(hostname)
|
||||
self.remote_addr = self.get_remote_addr(hostname)
|
||||
self.type = _type
|
||||
|
||||
@staticmethod
|
||||
def get_remote_addr(hostname):
|
||||
try:
|
||||
return socket.gethostbyname(hostname)
|
||||
except socket.gaierror:
|
||||
return '127.0.0.1'
|
||||
|
||||
def start_heartbeat_thread(self):
|
||||
print(f'- Start heartbeat thread => ({self.name})')
|
||||
t = threading.Thread(target=self.start_heartbeat)
|
||||
|
|
|
@ -26,10 +26,11 @@ class Handler(BaseHandler):
|
|||
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
||||
apply_system_users = self.ticket.meta.get('apply_system_users')
|
||||
apply_applications = self.ticket.meta.get('apply_applications')
|
||||
meta_display.update({
|
||||
'apply_system_users_display': [str(i) for i in SystemUser.objects.filter(id__in=apply_system_users)],
|
||||
'apply_applications_display': [str(i) for i in Application.objects.filter(id__in=apply_applications)]
|
||||
})
|
||||
with tmp_to_org(self.ticket.org_id):
|
||||
meta_display.update({
|
||||
'apply_system_users_display': [str(i) for i in SystemUser.objects.filter(id__in=apply_system_users)],
|
||||
'apply_applications_display': [str(i) for i in Application.objects.filter(id__in=apply_applications)]
|
||||
})
|
||||
|
||||
return meta_display
|
||||
|
||||
|
|
|
@ -24,10 +24,11 @@ class Handler(BaseHandler):
|
|||
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
||||
apply_assets = self.ticket.meta.get('apply_assets')
|
||||
apply_system_users = self.ticket.meta.get('apply_system_users')
|
||||
meta_display.update({
|
||||
'apply_assets_display': [str(i) for i in Asset.objects.filter(id__in=apply_assets)],
|
||||
'apply_system_users_display': [str(i)for i in SystemUser.objects.filter(id__in=apply_system_users)]
|
||||
})
|
||||
with tmp_to_org(self.ticket.org_id):
|
||||
meta_display.update({
|
||||
'apply_assets_display': [str(i) for i in Asset.objects.filter(id__in=apply_assets)],
|
||||
'apply_system_users_display': [str(i)for i in SystemUser.objects.filter(id__in=apply_system_users)]
|
||||
})
|
||||
return meta_display
|
||||
|
||||
# body
|
||||
|
|
|
@ -13,13 +13,15 @@ EMAIL_TEMPLATE = '''
|
|||
<div>
|
||||
<p>
|
||||
{title}
|
||||
<a href={ticket_detail_url}>
|
||||
<strong>{ticket_detail_url_description}</strong>
|
||||
</a>
|
||||
</p>
|
||||
<div>
|
||||
{body}
|
||||
</div>
|
||||
<div>
|
||||
<a href={ticket_detail_url}>
|
||||
<strong>{ticket_detail_url_description}</strong>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
'''
|
||||
|
||||
|
@ -36,13 +38,12 @@ class BaseTicketMessage(UserMessage):
|
|||
|
||||
def get_text_msg(self) -> dict:
|
||||
message = """
|
||||
{title}: {ticket_detail_url} -> {ticket_detail_url_description}
|
||||
{title}: {ticket_detail_url}
|
||||
{body}
|
||||
""".format(
|
||||
title=self.content_title,
|
||||
ticket_detail_url=self.ticket_detail_url,
|
||||
ticket_detail_url_description=_('click here to review'),
|
||||
body=self.ticket.body
|
||||
body=self.ticket.body.replace('<div style="margin-left: 20px;">', '').replace('</div>', '')
|
||||
)
|
||||
return {
|
||||
'subject': self.subject,
|
||||
|
|
|
@ -69,5 +69,9 @@ class ApplySerializer(serializers.Serializer):
|
|||
'Permission named `{}` already exists'.format(permission_name)
|
||||
))
|
||||
|
||||
|
||||
|
||||
def validate_apply_date_expired(self, apply_date_expired):
|
||||
apply_date_start = self.root.initial_data['meta'].get('apply_date_start')
|
||||
if str(apply_date_expired) <= apply_date_start:
|
||||
error = _('The expiration date should be greater than the start date')
|
||||
raise serializers.ValidationError(error)
|
||||
return apply_date_expired
|
||||
|
|
|
@ -60,3 +60,10 @@ class ApplySerializer(serializers.Serializer):
|
|||
raise serializers.ValidationError(_(
|
||||
'Permission named `{}` already exists'.format(permission_name)
|
||||
))
|
||||
|
||||
def validate_apply_date_expired(self, apply_date_expired):
|
||||
apply_date_start = self.root.initial_data['meta'].get('apply_date_start')
|
||||
if str(apply_date_expired) <= apply_date_start:
|
||||
error = _('The expiration date should be greater than the start date')
|
||||
raise serializers.ValidationError(error)
|
||||
return apply_date_expired
|
||||
|
|
|
@ -9,13 +9,15 @@ logger = get_logger(__file__)
|
|||
|
||||
|
||||
def send_ticket_applied_mail_to_assignees(ticket):
|
||||
assignees = ticket.current_node.first().ticket_assignees.all()
|
||||
if not assignees:
|
||||
logger.debug("Not found assignees, ticket: {}({}), assignees: {}".format(ticket, str(ticket.id), assignees))
|
||||
ticket_assignees = ticket.current_node.first().ticket_assignees.all()
|
||||
if not ticket_assignees:
|
||||
logger.debug(
|
||||
"Not found assignees, ticket: {}({}), assignees: {}".format(ticket, str(ticket.id), ticket_assignees)
|
||||
)
|
||||
return
|
||||
|
||||
for assignee in assignees:
|
||||
instance = TicketAppliedToAssignee(assignee, ticket)
|
||||
for ticket_assignee in ticket_assignees:
|
||||
instance = TicketAppliedToAssignee(ticket_assignee.assignee, ticket)
|
||||
if settings.DEBUG:
|
||||
logger.debug(instance)
|
||||
instance.publish_async()
|
||||
|
|
|
@ -13,11 +13,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='dingtalk_id',
|
||||
field=models.CharField(default=None, max_length=128, null=True, unique=True),
|
||||
field=models.CharField(default=None, max_length=128, null=True, unique=True, verbose_name='DingTalk'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='wecom_id',
|
||||
field=models.CharField(default=None, max_length=128, null=True, unique=True),
|
||||
field=models.CharField(default=None, max_length=128, null=True, unique=True, verbose_name='WeCom'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='feishu_id',
|
||||
field=models.CharField(default=None, max_length=128, null=True, unique=True),
|
||||
field=models.CharField(default=None, max_length=128, null=True, unique=True, verbose_name='FeiShu'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -656,9 +656,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
need_update_password = models.BooleanField(
|
||||
default=False, verbose_name=_('Need update password')
|
||||
)
|
||||
wecom_id = models.CharField(null=True, default=None, unique=True, max_length=128)
|
||||
dingtalk_id = models.CharField(null=True, default=None, unique=True, max_length=128)
|
||||
feishu_id = models.CharField(null=True, default=None, unique=True, max_length=128)
|
||||
wecom_id = models.CharField(null=True, default=None, unique=True, max_length=128, verbose_name=_('WeCom'))
|
||||
dingtalk_id = models.CharField(null=True, default=None, unique=True, max_length=128, verbose_name=_('DingTalk'))
|
||||
feishu_id = models.CharField(null=True, default=None, unique=True, max_length=128, verbose_name=_('FeiShu'))
|
||||
|
||||
def __str__(self):
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
|
|
|
@ -7,6 +7,11 @@ from notifications.notifications import UserMessage
|
|||
|
||||
|
||||
class ResetPasswordMsg(UserMessage):
|
||||
|
||||
def __init__(self, user):
|
||||
super().__init__(user)
|
||||
self.reset_passwd_token = user.generate_reset_token()
|
||||
|
||||
def get_text_msg(self) -> dict:
|
||||
user = self.user
|
||||
subject = _('Reset password')
|
||||
|
@ -30,7 +35,7 @@ Login direct 👇
|
|||
""") % {
|
||||
'name': user.name,
|
||||
'rest_password_url': reverse('authentication:reset-password', external=True),
|
||||
'rest_password_token': user.generate_reset_token(),
|
||||
'rest_password_token': self.reset_passwd_token,
|
||||
'forget_password_url': reverse('authentication:forgot-password', external=True),
|
||||
'email': user.email,
|
||||
'login_url': reverse('authentication:login', external=True),
|
||||
|
@ -62,7 +67,7 @@ Login direct 👇
|
|||
""") % {
|
||||
'name': user.name,
|
||||
'rest_password_url': reverse('authentication:reset-password', external=True),
|
||||
'rest_password_token': user.generate_reset_token(),
|
||||
'rest_password_token': self.reset_passwd_token,
|
||||
'forget_password_url': reverse('authentication:forgot-password', external=True),
|
||||
'email': user.email,
|
||||
'login_url': reverse('authentication:login', external=True),
|
||||
|
@ -98,10 +103,8 @@ If you have any questions, you can contact the administrator.
|
|||
|
||||
|
||||
IP Address: %(ip_address)s
|
||||
<br>
|
||||
<br>
|
||||
\n
|
||||
Browser: %(browser)s
|
||||
<br>
|
||||
|
||||
""") % {
|
||||
'name': user.name,
|
||||
|
|
|
@ -6,6 +6,7 @@ from rest_framework import serializers
|
|||
|
||||
from common.mixins import CommonBulkSerializerMixin
|
||||
from common.permissions import CanUpdateDeleteUser
|
||||
from common.validators import PhoneValidator
|
||||
from orgs.models import ROLE as ORG_ROLE
|
||||
from ..models import User
|
||||
from ..const import SystemOrOrgRole, PasswordStrategy
|
||||
|
@ -28,7 +29,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
is_expired = serializers.BooleanField(read_only=True, label=_('Is expired'))
|
||||
can_update = serializers.SerializerMethodField(label=_('Can update'))
|
||||
can_delete = serializers.SerializerMethodField(label=_('Can delete'))
|
||||
can_public_key_auth = serializers.ReadOnlyField(source='can_use_ssh_key_login')
|
||||
can_public_key_auth = serializers.ReadOnlyField(source='can_use_ssh_key_login', label=_('Can public key authentication'))
|
||||
org_roles = serializers.ListField(
|
||||
label=_('Organization role name'), allow_null=True, required=False,
|
||||
child=serializers.ChoiceField(choices=ORG_ROLE.choices), default=["User"]
|
||||
|
@ -51,6 +52,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
'date_expired', 'date_joined', 'last_login', # 日期字段
|
||||
'created_by', 'comment', # 通用字段
|
||||
'is_wecom_bound', 'is_dingtalk_bound', 'is_feishu_bound',
|
||||
'wecom_id', 'dingtalk_id', 'feishu_id'
|
||||
]
|
||||
# 包含不太常用的字段,可以没有
|
||||
fields_verbose = fields_small + [
|
||||
|
@ -86,6 +88,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
'is_wecom_bound': {'label': _('Is wecom bound')},
|
||||
'is_dingtalk_bound': {'label': _('Is dingtalk bound')},
|
||||
'is_feishu_bound': {'label': _('Is feishu bound')},
|
||||
'phone': {'validators': [PhoneValidator()]},
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
Loading…
Reference in New Issue