Compare commits

...

47 Commits

Author SHA1 Message Date
xinwen
9c9261e34d fix(orgs): 修复组织添加用户bug 2020-10-14 22:49:36 -05:00
xinwen
3353fbd06b fix(orgs): 组织添加成员bug 2020-10-14 06:13:15 -05:00
xinwen
628c034c53 feat(auth): sso 生成的地址重复访问的时候,重定向到用户指定的 next 地址 2020-10-08 22:23:27 -05:00
ibuler
8e6e8a0cbd fix: 修复资产列表有时的bug 2020-09-29 23:51:11 -05:00
xinwen
493e61aa34 fix(orgs): 用户修改组织角色报错 2020-09-29 23:46:45 -05:00
Jiangjie.Bai
cdf3cf3e8f Merge pull request #4667 from jumpserver/dev
fix(command): 修复命令导出选中项问题
2020-09-16 19:55:18 +08:00
Bai
118564577e fix(command): 修复命令导出选中项问题 2020-09-16 19:53:58 +08:00
Jiangjie.Bai
47f2df0a5b Merge pull request #4665 from jumpserver/dev
fix(command): 修复命令导出选中项问题
2020-09-16 19:38:16 +08:00
Bai
e4aafc236d fix(command): 修复命令导出选中项问题 2020-09-16 19:36:21 +08:00
Jiangjie.Bai
b1c530bba8 Merge pull request #4661 from jumpserver/dev
Dev
2020-09-16 19:03:38 +08:00
Bai
95aa9781c3 fix(command): 修复命令导出选中项会导出全部的问题 2020-09-16 19:02:49 +08:00
xinwen
9f6540afe3 fix(tickets): 调整登录确认工单 title 2020-09-16 18:03:15 +08:00
ibuler
832bb832ce fix(authentication): 修复cas退出的bug 2020-09-16 17:53:01 +08:00
ibuler
501329a8db fix: 再次修复 2020-09-16 17:51:45 +08:00
ibuler
8913aacd1e fix(authentication): 修复同时开启radius, openid引起的问题 2020-09-16 17:51:45 +08:00
xinwen
e461fbdf50 fix(tickets): 修复已处理工单的 待处理人 字段 2020-09-16 17:47:26 +08:00
peijianbo
3941539408 fix(authentication):修复开启二次认证时,地址跳转出错问题 2020-09-16 16:46:28 +08:00
xinwen
605db2d905 fix(auth): 调整登录复核工单 title 2020-09-16 15:31:20 +08:00
Jiangjie.Bai
1ef3f24465 Merge pull request #4648 from jumpserver/dev
chore: merge dev to master
2020-09-15 17:23:48 +08:00
peijianbo
4090a0b123 feat(uathentication):登录表单回车可直接提交表单 2020-09-15 17:10:52 +08:00
ibuler
a55e28fc87 perf: 优化ldap超时时间 2020-09-15 15:26:18 +08:00
ibuler
82cf53181f perf(settings): 修改默认超时时间为10s 2020-09-15 15:26:18 +08:00
ibuler
78232aa900 perf(terminal): 优化命令提交 2020-09-14 19:25:50 +08:00
ibuler
d2c93aff66 feat: 可以关闭工单菜单 2020-09-14 18:25:47 +08:00
peijianbo
516e2309c0 bug(authentication): 登录表单仅提交时加密(xpack) 2020-09-14 17:28:49 +08:00
peijianbo
4688e46f97 feat(authentication):将cas认证通过的登录日志记录到系统 2020-09-14 12:46:12 +08:00
peijianbo
1299f3da75 feat(authentication):登录表单仅提交时加密 2020-09-14 12:45:44 +08:00
Bai
fe502cbe41 fix(assets): 修复系统用户导入模版没有密码字段的问题 2020-09-14 12:43:39 +08:00
xinwen
09bfac34f1 fix(orgs): 修复 org-memeber-relation POST 报错 2020-09-14 11:00:10 +08:00
Jiangjie.Bai
12a86d7244 Merge pull request #4611 from jumpserver/dev
Dev
2020-09-08 20:08:53 +08:00
Jiangjie.Bai
269eea8802 Merge branch 'master' into dev 2020-09-08 19:32:38 +08:00
老广
72aa265dd7 doc: 修改readme,添加子项目连接 (#4602)
* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md
2020-09-08 14:31:54 +08:00
老广
e26716e1e1 Update README.md
docs: 添加  developer wanted
2020-09-08 14:30:31 +08:00
Bai
80b9db417c feat(ldap): 获取ldap用户列表,采用线程方式 2020-09-08 11:57:45 +08:00
Bai
d944b5f4ff feat(tickets): 工单添加comment字段 2020-09-07 20:00:09 +08:00
peijianbo
1b84afee0c feat(audits):修改日志默认保存时间(90->9999) 2020-09-07 18:35:19 +08:00
fit2bot
172b6edd28 feat(user):同一个账号仅允许在一台终端设备登录 (#4590)
* feat(user):同一个账号仅允许在一台终端设备登录

* feat(user):同一个账号仅允许在一台终端设备登录

* feat(user):同一个账号仅允许在一台终端设备登录

* feat(user):同一个账号仅允许在一台终端设备登录

* feat(user):同一个账号仅允许在一台终端设备登录

Co-authored-by: peijianbo <peijainbo3006@163.com>
2020-09-07 17:42:59 +08:00
Bai
e6f248bfa0 feat(i18n): 添加云同步实例任务hostname_strategy字段翻译信息 2020-09-07 17:39:35 +08:00
ibuler
1f037b1933 feat(i18n): 添加新翻译 2020-09-02 15:21:48 +08:00
Bai
ae9bbd2683 fix(common) 修复管理员未设置Email主题前缀导致发送邮件失败的问题 2020-09-02 10:23:44 +08:00
xinwen
a0085c4eab feat(README): 添加企业版试用链接 2020-09-01 16:50:43 +08:00
ibuler
ddb71c43c4 fix(users): 修复用户在不同组织引起的问题 2020-09-01 16:47:13 +08:00
herealways
8227f44058 feat: 添加AES GCM模式为默认的加密方式 2020-09-01 14:48:40 +08:00
ibuler
e81762d692 ci(Dockerfile): 修改依赖的setuptools版本,导致的ldap无法安装问题 2020-09-01 13:39:08 +08:00
老广
5b8fa1809c Update README.md
docs: 添加  developer wanted
2020-08-24 10:04:03 +08:00
BaiJiangJie
90ba6442dd Merge pull request #4523 from jumpserver/dev
fix(orgs): 完善组织与用户变化时的信号
2020-08-20 16:40:07 +08:00
xinwen
a28334b6d8 fix(orgs): 完善组织与用户变化时的信号 2020-08-20 16:34:10 +08:00
34 changed files with 412 additions and 203 deletions

View File

@@ -21,7 +21,7 @@ RUN useradd jumpserver
RUN yum -y install epel-release && \
echo -e "[mysql]\nname=mysql\nbaseurl=${MYSQL_MIRROR}\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
RUN yum -y install $(cat requirements/rpm_requirements.txt)
RUN pip install --upgrade pip setuptools wheel -i ${PIP_MIRROR} && \
RUN pip install --upgrade pip setuptools==49.6.0 wheel -i ${PIP_MIRROR} && \
pip config set global.index-url ${PIP_MIRROR}
RUN pip install -r requirements/requirements.txt || pip install -r requirements/requirements.txt

View File

@@ -4,6 +4,10 @@
[![Django](https://img.shields.io/badge/django-2.2-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Docker Pulls](https://img.shields.io/docker/pulls/jumpserver/jms_all.svg)](https://hub.docker.com/u/jumpserver)
|Developer Wanted|
|------------------|
|JumpServer 正在寻找开发者,一起为改变世界做些贡献吧,哪怕一点点,联系我 <ibuler@fit2cloud.com> |
JumpServer 是全球首款开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 规范的运维安全审计系统。
JumpServer 使用 Python / Django 为主进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。
@@ -202,6 +206,16 @@ v2.1.0 是 v2.0.0 之后的功能版本。
- [完整文档](https://docs.jumpserver.org)
- [演示视频](https://jumpserver.oss-cn-hangzhou.aliyuncs.com/jms-media/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91Jumpserver%20%E5%A0%A1%E5%9E%92%E6%9C%BA%20V1.5.0%20%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%20-%20final.mp4)
## 组件项目
- [Lina](https://github.com/jumpserver/lina) JumpServer Web UI 项目
- [Luna](https://github.com/jumpserver/luna) JumpServer Web Terminal 项目
- [Koko](https://github.com/jumpserver/koko) JumpServer 字符协议 Connector 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco)
- [Guacamole](https://github.com/jumpserver/docker-guacamole) JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/)
## JumpServer 企业版
- [申请企业版试用](https://jinshuju.net/f/kyOYpi)
> 注:企业版支持离线安装,申请通过后会提供高速下载链接。
## 案例研究
- [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)

View File

@@ -149,6 +149,7 @@ class SystemUserListSerializer(SystemUserSerializer):
class Meta(SystemUserSerializer.Meta):
fields = [
'id', 'name', 'username', 'protocol',
'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display',
'priority', "username_same_with_user",
'auto_push', 'sudo', 'shell', 'comment',
@@ -157,6 +158,12 @@ class SystemUserListSerializer(SystemUserSerializer):
'sftp_root',
]
extra_kwargs = {
'password': {"write_only": True},
'public_key': {"write_only": True},
'private_key': {"write_only": True},
}
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """

View File

@@ -125,6 +125,8 @@ class TreeService(Tree):
def assets(self, nid):
node = self.get_node(nid)
if not node:
return set()
return node.data.get("assets", set())
def valid_assets(self, nid):
@@ -132,6 +134,8 @@ class TreeService(Tree):
def all_assets(self, nid):
node = self.get_node(nid)
if not node:
return set()
if node.data is None:
node.data = {}
all_assets = node.data.get("all_assets")

View File

@@ -16,7 +16,7 @@ def clean_login_log_period():
try:
days = int(settings.LOGIN_LOG_KEEP_DAYS)
except ValueError:
days = 90
days = 9999
expired_day = now - datetime.timedelta(days=days)
UserLoginLog.objects.filter(datetime__lt=expired_day).delete()
@@ -28,6 +28,6 @@ def clean_operation_log_period():
try:
days = int(settings.LOGIN_LOG_KEEP_DAYS)
except ValueError:
days = 90
days = 9999
expired_day = now - datetime.timedelta(days=days)
OperateLog.objects.filter(datetime__lt=expired_day).delete()

View File

@@ -73,12 +73,12 @@ class SSOViewSet(AuthMixin, JmsGenericViewSet):
token.save()
except (ValueError, SSOToken.DoesNotExist):
self.send_auth_signal(success=False, reason='authkey_invalid')
return HttpResponseRedirect(reverse('authentication:login'))
return HttpResponseRedirect(next_url)
# 判断是否过期
if (utcnow().timestamp() - token.date_created.timestamp()) > settings.AUTH_SSO_AUTHKEY_TTL:
self.send_auth_signal(success=False, reason='authkey_timeout')
return HttpResponseRedirect(reverse('authentication:login'))
return HttpResponseRedirect(next_url)
user = token.user
login(self.request, user, 'authentication.backends.api.SSOAuthentication')

View File

@@ -202,4 +202,6 @@ class SSOAuthentication(ModelBackend):
"""
什么也不做呀😺
"""
pass
def authenticate(self, request, sso_token=None, **kwargs):
pass

View File

@@ -2,7 +2,7 @@
#
import traceback
from django.contrib.auth import get_user_model
from django.contrib.auth import get_user_model, authenticate
from radiusauth.backends import RADIUSBackend, RADIUSRealmBackend
from django.conf import settings
@@ -38,16 +38,12 @@ class CreateUserMixin:
return [], False, False
return None
def authenticate(self, *args, **kwargs):
# 校验用户时会传入public_key参数父类authentication中不接受public_key参数所以要pop掉
# TODO:需要优化各backend的authenticate方法django进行调用前会检测各authenticate的参数
kwargs.pop('public_key', None)
return super().authenticate(*args, **kwargs)
class RadiusBackend(CreateUserMixin, RADIUSBackend):
pass
def authenticate(self, request, username='', password='', **kwargs):
return super().authenticate(request, username=username, password=password)
class RadiusRealmBackend(CreateUserMixin, RADIUSRealmBackend):
pass
def authenticate(self, request, username='', password='', realm=None, **kwargs):
return super().authenticate(request, username=username, password=password, realm=realm)

View File

@@ -53,7 +53,7 @@ class LoginConfirmSetting(CommonModelMixin):
def create_confirm_ticket(self, request=None):
from tickets.models import Ticket
title = _('Login confirm') + '{}'.format(self.user)
title = _('Login confirm') + ' {}'.format(self.user)
if request:
remote_addr = get_request_ip(request)
city = get_ip_city(remote_addr)

View File

@@ -1,10 +1,27 @@
from importlib import import_module
from django.conf import settings
from django.contrib.auth import user_logged_in
from django.core.cache import cache
from django.dispatch import receiver
from django_cas_ng.signals import cas_user_authenticated
from jms_oidc_rp.signals import openid_user_login_failed, openid_user_login_success
from .signals import post_auth_success, post_auth_failed
@receiver(user_logged_in)
def on_user_auth_login_success(sender, user, request, **kwargs):
if settings.USER_LOGIN_SINGLE_MACHINE_ENABLED:
user_id = 'single_machine_login_' + str(user.id)
session_key = cache.get(user_id)
if session_key and session_key != request.session.session_key:
session = import_module(settings.SESSION_ENGINE).SessionStore(session_key)
session.delete()
cache.set(user_id, request.session.session_key, None)
@receiver(openid_user_login_success)
def on_oidc_user_login_success(sender, request, user, **kwargs):
post_auth_success.send(sender, user=user, request=request)
@@ -13,3 +30,8 @@ def on_oidc_user_login_success(sender, request, user, **kwargs):
@receiver(openid_user_login_failed)
def on_oidc_user_login_failed(sender, username, request, reason, **kwargs):
post_auth_failed.send(sender, username=username, request=request, reason=reason)
@receiver(cas_user_authenticated)
def on_cas_user_login_success(sender, request, user, **kwargs):
post_auth_success.send(sender, user=user, request=request)

View File

@@ -26,7 +26,8 @@
{% endif %}
</div>
<div class="form-group">
<input type="password" class="form-control" id="password" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
<input type="password" class="form-control" id="password" placeholder="{% trans 'Password' %}" required="">
<input id="password-hidden" type="text" style="display:none" name="{{ form.password.html_name }}">
{% if form.errors.password %}
<div class="help-block field-error">
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
@@ -86,7 +87,7 @@
var rsaPublicKey = "{{ rsa_public_key }}"
var password =$('#password').val(); //明文密码
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
$('#password').val(passwordEncrypted); //返回给密码输入input
$('#password-hidden').val(passwordEncrypted); //返回给密码输入input
$('#form').submit();//post提交
}
</script>

View File

@@ -106,7 +106,8 @@
{% endif %}
</div>
<div class="form-group">
<input type="password" class="form-control" id="password" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
<input type="password" class="form-control" id="password" placeholder="{% trans 'Password' %}" required="">
<input id="password-hidden" type="text" style="display:none" name="{{ form.password.html_name }}">
{% if form.errors.password %}
<div class="help-block field-error">
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
@@ -157,7 +158,7 @@
var rsaPublicKey = "{{ rsa_public_key }}"
var password =$('#password').val(); //明文密码
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
$('#password').val(passwordEncrypted); //返回给密码输入input
$('#password-hidden').val(passwordEncrypted); //返回给密码输入input
$('#contact-form').submit();//post提交
}
</script>

View File

@@ -17,6 +17,7 @@ from django.views.generic.base import TemplateView, RedirectView
from django.views.generic.edit import FormView
from django.conf import settings
from django.urls import reverse_lazy
from django.contrib.auth import BACKEND_SESSION_KEY
from common.const.front_urls import TICKET_DETAIL
from common.utils import get_request_ip, get_object_or_none
@@ -205,12 +206,12 @@ class UserLoginWaitConfirmView(TemplateView):
class UserLogoutView(TemplateView):
template_name = 'flash_message_standalone.html'
@staticmethod
def get_backend_logout_url():
if settings.AUTH_OPENID:
def get_backend_logout_url(self):
backend = self.request.session.get(BACKEND_SESSION_KEY, '')
if 'OIDC' in backend:
return settings.AUTH_OPENID_AUTH_LOGOUT_URL_NAME
# if settings.AUTH_CAS:
# return settings.CAS_LOGOUT_URL_NAME
elif 'CAS' in backend:
return settings.CAS_LOGOUT_URL_NAME
return None
def get(self, request, *args, **kwargs):

View File

@@ -5,7 +5,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text
from ..utils import signer, aes_crypto
from ..utils import signer, aes_crypto, aes_ecb_crypto
__all__ = [
@@ -117,9 +117,17 @@ class EncryptMixin:
return signer.unsign(value) or ''
def decrypt_from_aes(self, value):
"""
先尝试使用GCM模式解密如果解不开再尝试使用原来的ECB模式解密
"""
try:
return aes_crypto.decrypt(value)
except (TypeError, ValueError):
except ValueError:
pass
try:
return aes_ecb_crypto.decrypt(value)
except (TypeError, ValueError, UnicodeDecodeError):
pass
def from_db_value(self, value, expression, connection, context):

View File

@@ -24,7 +24,7 @@ def send_mail_async(*args, **kwargs):
"""
if len(args) == 3:
args = list(args)
args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
args[0] = (settings.EMAIL_SUBJECT_PREFIX or '') + args[0]
email_from = settings.EMAIL_FROM or settings.EMAIL_HOST_USER
args.insert(2, email_from)
args = tuple(args)

View File

@@ -1,5 +1,7 @@
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
from django.conf import settings
@@ -44,11 +46,69 @@ class AESCrypto:
return str(aes.decrypt(base64.decodebytes(bytes(text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密
def get_aes_crypto(key=None):
class AESCryptoGCM:
"""
使用AES GCM模式
"""
def __init__(self, key):
self.key = self.process_key(key)
@staticmethod
def process_key(key):
"""
返回32 bytes 的key
"""
if not isinstance(key, bytes):
key = bytes(key, encoding='utf-8')
if len(key) >= 32:
return key[:32]
return pad(key, 32)
def encrypt(self, text):
"""
加密text并将 header, nonce, tag (3*16 bytes, base64后变为 3*24 bytes)
附在密文前。解密时要用到。
"""
header = get_random_bytes(16)
cipher = AES.new(self.key, AES.MODE_GCM)
cipher.update(header)
ciphertext, tag = cipher.encrypt_and_digest(bytes(text, encoding='utf-8'))
result = []
for byte_data in (header, cipher.nonce, tag, ciphertext):
result.append(base64.b64encode(byte_data).decode('utf-8'))
return ''.join(result)
def decrypt(self, text):
"""
提取header, nonce, tag并解密text。
"""
metadata = text[:72]
header = base64.b64decode(metadata[:24])
nonce = base64.b64decode(metadata[24:48])
tag = base64.b64decode(metadata[48:])
ciphertext = base64.b64decode(text[72:])
cipher = AES.new(self.key, AES.MODE_GCM, nonce=nonce)
cipher.update(header)
plain_text_bytes = cipher.decrypt_and_verify(ciphertext, tag)
return plain_text_bytes.decode('utf-8')
def get_aes_crypto(key=None, mode='GCM'):
if key is None:
key = settings.SECRET_KEY
a = AESCrypto(key)
if mode == 'ECB':
a = AESCrypto(key)
elif mode == 'GCM':
a = AESCryptoGCM(key)
return a
aes_crypto = get_aes_crypto()
aes_ecb_crypto = get_aes_crypto(mode='ECB')
aes_crypto = get_aes_crypto(mode='GCM')

View File

@@ -163,7 +163,7 @@ class Config(dict):
'AUTH_LDAP_SEARCH_FILTER': '(cn=%(user)s)',
'AUTH_LDAP_START_TLS': False,
'AUTH_LDAP_USER_ATTR_MAP': {"username": "cn", "name": "sn", "email": "mail"},
'AUTH_LDAP_CONNECT_TIMEOUT': 30,
'AUTH_LDAP_CONNECT_TIMEOUT': 10,
'AUTH_LDAP_SEARCH_PAGED_SIZE': 1000,
'AUTH_LDAP_SYNC_IS_PERIODIC': False,
'AUTH_LDAP_SYNC_INTERVAL': None,
@@ -266,7 +266,9 @@ class Config(dict):
'ORG_CHANGE_TO_URL': '',
'LANGUAGE_CODE': 'zh',
'TIME_ZONE': 'Asia/Shanghai',
'CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED': True
'CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED': True,
'USER_LOGIN_SINGLE_MACHINE_ENABLED': False,
'TICKETS_ENABLED': True
}
def compatible_auth_openid_of_key(self):

View File

@@ -32,7 +32,8 @@ if os.path.isfile(LDAP_CERT_FILE):
# AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
# )
AUTH_LDAP_CONNECTION_OPTIONS = {
ldap.OPT_TIMEOUT: CONFIG.AUTH_LDAP_CONNECT_TIMEOUT
ldap.OPT_TIMEOUT: CONFIG.AUTH_LDAP_CONNECT_TIMEOUT,
ldap.OPT_NETWORK_TIMEOUT: CONFIG.AUTH_LDAP_CONNECT_TIMEOUT
}
AUTH_LDAP_CACHE_TIMEOUT = 1
AUTH_LDAP_ALWAYS_UPDATE_USER = True
@@ -89,6 +90,7 @@ CAS_LOGIN_URL_NAME = "authentication:cas:cas-login"
CAS_LOGOUT_URL_NAME = "authentication:cas:cas-logout"
CAS_LOGIN_MSG = None
CAS_LOGGED_MSG = None
CAS_IGNORE_REFERER = True
CAS_LOGOUT_COMPLETELY = CONFIG.CAS_LOGOUT_COMPLETELY
CAS_VERSION = CONFIG.CAS_VERSION
CAS_ROOT_PROXIED_AS = CONFIG.CAS_ROOT_PROXIED_AS

View File

@@ -70,6 +70,9 @@ FLOWER_URL = CONFIG.FLOWER_URL
# Enable internal period task
PERIOD_TASK_ENABLED = CONFIG.PERIOD_TASK_ENABLED
# only allow single machine login with the same account
USER_LOGIN_SINGLE_MACHINE_ENABLED = CONFIG.USER_LOGIN_SINGLE_MACHINE_ENABLED
# Email custom content
EMAIL_SUBJECT_PREFIX = DYNAMIC.EMAIL_SUBJECT_PREFIX
EMAIL_SUFFIX = DYNAMIC.EMAIL_SUFFIX
@@ -98,3 +101,5 @@ LOGO_URLS = DYNAMIC.LOGO_URLS
CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED
DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S'
TICKETS_ENABLED = CONFIG.TICKETS_ENABLED

Binary file not shown.

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-19 18:10+0800\n"
"POT-Creation-Date: 2020-09-07 16:23+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@@ -29,7 +29,7 @@ msgstr "自定义"
#: orgs/models.py:22 perms/models/base.py:48 settings/models.py:27
#: terminal/models.py:27 terminal/models.py:344 terminal/models.py:376
#: terminal/models.py:413 users/forms/profile.py:20 users/models/group.py:15
#: users/models/user.py:491 users/templates/users/_select_user_modal.html:13
#: users/models/user.py:501 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_asset_permission.html:37
#: users/templates/users/user_asset_permission.html:154
#: users/templates/users/user_database_app_permission.html:36
@@ -42,7 +42,7 @@ msgstr "自定义"
#: users/templates/users/user_profile.html:51
#: users/templates/users/user_pubkey_update.html:57
#: users/templates/users/user_remote_app_permission.html:36
#: xpack/plugins/cloud/models.py:35
#: xpack/plugins/cloud/models.py:36
msgid "Name"
msgstr "名称"
@@ -79,14 +79,14 @@ msgstr "数据库"
#: assets/models/label.py:23 ops/models/adhoc.py:37 orgs/models.py:25
#: perms/models/base.py:56 settings/models.py:32 terminal/models.py:37
#: terminal/models.py:383 terminal/models.py:420 users/models/group.py:16
#: users/models/user.py:524 users/templates/users/user_detail.html:115
#: users/models/user.py:534 users/templates/users/user_detail.html:115
#: users/templates/users/user_granted_database_app.html:38
#: users/templates/users/user_granted_remote_app.html:37
#: users/templates/users/user_group_detail.html:62
#: users/templates/users/user_group_list.html:16
#: users/templates/users/user_profile.html:138
#: xpack/plugins/change_auth_plan/models.py:77 xpack/plugins/cloud/models.py:53
#: xpack/plugins/cloud/models.py:140 xpack/plugins/gathered_user/models.py:26
#: xpack/plugins/change_auth_plan/models.py:77 xpack/plugins/cloud/models.py:54
#: xpack/plugins/cloud/models.py:149 xpack/plugins/gathered_user/models.py:26
msgid "Comment"
msgstr "备注"
@@ -125,7 +125,7 @@ msgstr "Kubernetes应用"
#: users/templates/users/user_asset_permission.html:70
#: users/templates/users/user_granted_remote_app.html:36
#: xpack/plugins/change_auth_plan/models.py:283
#: xpack/plugins/cloud/models.py:266
#: xpack/plugins/cloud/models.py:275
msgid "Asset"
msgstr "资产"
@@ -147,10 +147,10 @@ msgstr "参数"
#: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:60
#: assets/models/group.py:21 common/db/models.py:67 common/mixins/models.py:49
#: orgs/models.py:23 orgs/models.py:389 perms/models/base.py:54
#: users/models/user.py:532 users/serializers/group.py:35
#: users/models/user.py:542 users/serializers/group.py:35
#: users/templates/users/user_detail.html:97
#: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:56
#: xpack/plugins/cloud/models.py:146 xpack/plugins/gathered_user/models.py:30
#: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:57
#: xpack/plugins/cloud/models.py:155 xpack/plugins/gathered_user/models.py:30
msgid "Created by"
msgstr "创建者"
@@ -163,7 +163,7 @@ msgstr "创建者"
#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27
#: orgs/models.py:24 orgs/models.py:387 perms/models/base.py:55
#: users/models/group.py:18 users/templates/users/user_group_detail.html:58
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:149
#: xpack/plugins/cloud/models.py:60 xpack/plugins/cloud/models.py:158
msgid "Date created"
msgstr "创建日期"
@@ -196,7 +196,7 @@ msgstr "不能移除资产的管理用户账号"
msgid "Latest version could not be delete"
msgstr "最新版本的不能被删除"
#: assets/models/asset.py:146 xpack/plugins/cloud/providers/base.py:16
#: assets/models/asset.py:146 xpack/plugins/cloud/providers/base.py:17
msgid "Base"
msgstr "基础"
@@ -262,7 +262,7 @@ msgstr "激活"
#: assets/models/asset.py:199 assets/models/cluster.py:19
#: assets/models/user.py:66 templates/_nav.html:44
#: xpack/plugins/cloud/models.py:133 xpack/plugins/cloud/serializers.py:83
#: xpack/plugins/cloud/models.py:142 xpack/plugins/cloud/serializers.py:84
msgid "Admin user"
msgstr "管理用户"
@@ -354,7 +354,7 @@ msgstr ""
#: audits/models.py:99 authentication/forms.py:11
#: authentication/templates/authentication/login.html:21
#: authentication/templates/authentication/xpack_login.html:101
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:489
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:499
#: users/templates/users/_select_user_modal.html:14
#: users/templates/users/user_detail.html:53
#: users/templates/users/user_list.html:15
@@ -407,7 +407,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:510
#: assets/models/cluster.py:22 users/models/user.py:520
#: users/templates/users/user_detail.html:62
msgid "Phone"
msgstr "手机"
@@ -433,7 +433,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:657
#: users/models/user.py:661
msgid "System"
msgstr "系统"
@@ -547,7 +547,7 @@ msgstr "默认资产组"
#: assets/models/label.py:15 audits/models.py:36 audits/models.py:56
#: audits/models.py:69 audits/serializers.py:77 authentication/models.py:46
#: authentication/models.py:90 orgs/models.py:16 orgs/models.py:385
#: authentication/models.py:90 orgs/models.py:17 orgs/models.py:385
#: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38
#: perms/forms/remote_app_permission.py:40 perms/models/base.py:49
#: templates/index.html:78 terminal/backends/command/models.py:18
@@ -555,7 +555,7 @@ msgstr "默认资产组"
#: tickets/models/ticket.py:30 tickets/models/ticket.py:137
#: tickets/serializers/request_asset_perm.py:65
#: tickets/serializers/ticket.py:31 users/forms/group.py:15
#: users/models/user.py:158 users/models/user.py:645
#: users/models/user.py:159 users/models/user.py:649
#: users/serializers/group.py:20
#: users/templates/users/user_asset_permission.html:38
#: users/templates/users/user_asset_permission.html:64
@@ -603,7 +603,7 @@ msgstr "键"
#: users/templates/users/user_asset_permission.html:41
#: users/templates/users/user_asset_permission.html:73
#: users/templates/users/user_asset_permission.html:158
#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/serializers.py:84
#: xpack/plugins/cloud/models.py:138 xpack/plugins/cloud/serializers.py:85
msgid "Node"
msgstr "节点"
@@ -733,14 +733,14 @@ msgid "Backend"
msgstr "后端"
#: assets/serializers/asset_user.py:75 users/forms/profile.py:148
#: users/models/user.py:521 users/templates/users/user_password_update.html:48
#: users/models/user.py:531 users/templates/users/user_password_update.html:48
#: users/templates/users/user_profile.html:69
#: users/templates/users/user_profile_update.html:46
#: users/templates/users/user_pubkey_update.html:46
msgid "Public key"
msgstr "SSH公钥"
#: assets/serializers/asset_user.py:79 users/models/user.py:518
#: assets/serializers/asset_user.py:79 users/models/user.py:528
msgid "Private key"
msgstr "ssh私钥"
@@ -1002,7 +1002,7 @@ msgstr "启用"
msgid "-"
msgstr ""
#: audits/models.py:96 xpack/plugins/cloud/models.py:201
#: audits/models.py:96 xpack/plugins/cloud/models.py:210
msgid "Failed"
msgstr "失败"
@@ -1025,20 +1025,20 @@ msgstr "Agent"
#: audits/models.py:104
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: authentication/templates/authentication/login_otp.html:6
#: users/forms/profile.py:52 users/models/user.py:513
#: users/serializers/user.py:240 users/templates/users/user_detail.html:77
#: users/forms/profile.py:52 users/models/user.py:523
#: users/serializers/user.py:229 users/templates/users/user_detail.html:77
#: users/templates/users/user_profile.html:87
msgid "MFA"
msgstr "多因子认证"
#: audits/models.py:105 xpack/plugins/change_auth_plan/models.py:304
#: xpack/plugins/cloud/models.py:214
#: xpack/plugins/cloud/models.py:223
msgid "Reason"
msgstr "原因"
#: audits/models.py:106 tickets/serializers/request_asset_perm.py:63
#: tickets/serializers/ticket.py:29 xpack/plugins/cloud/models.py:211
#: xpack/plugins/cloud/models.py:269
#: tickets/serializers/ticket.py:29 xpack/plugins/cloud/models.py:220
#: xpack/plugins/cloud/models.py:278
msgid "Status"
msgstr "状态"
@@ -1051,7 +1051,7 @@ msgid "Is success"
msgstr "是否成功"
#: audits/serializers.py:73 ops/models/command.py:24
#: xpack/plugins/cloud/models.py:209
#: xpack/plugins/cloud/models.py:218
msgid "Result"
msgstr "结果"
@@ -1199,7 +1199,7 @@ msgstr "登录复核 {}"
msgid "SSO auth closed"
msgstr "SSO 认证关闭了"
#: authentication/errors.py:218 authentication/views/login.py:243
#: authentication/errors.py:218 authentication/views/login.py:244
msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改"
@@ -1265,7 +1265,7 @@ msgid "Show"
msgstr "显示"
#: authentication/templates/authentication/_access_key_modal.html:66
#: users/models/user.py:411 users/serializers/user.py:237
#: users/models/user.py:421 users/serializers/user.py:226
#: users/templates/users/user_profile.html:94
#: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:166
@@ -1274,7 +1274,7 @@ msgid "Disable"
msgstr "禁用"
#: authentication/templates/authentication/_access_key_modal.html:67
#: users/models/user.py:412 users/serializers/user.py:238
#: users/models/user.py:422 users/serializers/user.py:227
#: users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:170
msgid "Enable"
@@ -1382,11 +1382,11 @@ msgstr "复制成功"
msgid "Welcome back, please enter username and password to login"
msgstr "欢迎回来,请输入用户名和密码登录"
#: authentication/views/login.py:84
#: authentication/views/login.py:85
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:189
#: authentication/views/login.py:190
msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
" Don't close this page"
@@ -1394,19 +1394,19 @@ msgstr ""
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
" 不要关闭本页面"
#: authentication/views/login.py:194
#: authentication/views/login.py:195
msgid "No ticket found"
msgstr "没有发现工单"
#: authentication/views/login.py:226
#: authentication/views/login.py:227
msgid "Logout success"
msgstr "退出登录成功"
#: authentication/views/login.py:227
#: authentication/views/login.py:228
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: authentication/views/login.py:242
#: authentication/views/login.py:243
msgid "Please change your password"
msgstr "请修改密码"
@@ -1466,7 +1466,7 @@ msgstr ""
msgid "Marshal data to text field"
msgstr ""
#: common/fields/model.py:157
#: common/fields/model.py:165
msgid "Encrypt field using Secret Key"
msgstr ""
@@ -1503,11 +1503,11 @@ msgstr ""
"<div>Luna是单独部署的一个程序你需要部署lunakoko, </div><div>如果你看到了"
"这个页面证明你访问的不是nginx监听的端口祝你好运</div>"
#: jumpserver/views/other.py:76
#: jumpserver/views/other.py:77
msgid "Websocket server run on port: {}, you should proxy it on nginx"
msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置"
#: jumpserver/views/other.py:90
#: jumpserver/views/other.py:91
msgid ""
"<div>Koko is a separately deployed program, you need to deploy Koko, "
"configure nginx for url distribution,</div> </div>If you see this page, "
@@ -1693,11 +1693,11 @@ msgstr "组织"
msgid "Organization administrator"
msgstr "组织管理员"
#: orgs/models.py:17
#: orgs/models.py:16
msgid "Organization auditor"
msgstr "组织审计员"
#: orgs/models.py:386 users/forms/user.py:27 users/models/user.py:501
#: orgs/models.py:386 users/forms/user.py:27 users/models/user.py:511
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:73
#: users/templates/users/user_list.html:16
@@ -1722,8 +1722,7 @@ msgstr "提示RDP 协议不支持单独控制上传或下载文件"
#: perms/forms/asset_permission.py:86 perms/forms/database_app_permission.py:41
#: perms/forms/remote_app_permission.py:43 perms/models/base.py:50
#: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31
#: users/models/user.py:497 users/serializers/user.py:48
#: users/templates/users/_select_user_modal.html:16
#: users/models/user.py:507 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_asset_permission.html:39
#: users/templates/users/user_asset_permission.html:67
#: users/templates/users/user_database_app_permission.html:38
@@ -1789,7 +1788,7 @@ msgid "Asset permission"
msgstr "资产授权"
#: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:31
#: users/models/user.py:529 users/templates/users/user_detail.html:93
#: users/models/user.py:539 users/templates/users/user_detail.html:93
#: users/templates/users/user_profile.html:120
msgid "Date expired"
msgstr "失效日期"
@@ -2537,37 +2536,37 @@ msgstr "结束日期"
msgid "Args"
msgstr "参数"
#: tickets/api/request_asset_perm.py:46
#: tickets/api/request_asset_perm.py:48
#, python-format
msgid "Ticket has %s"
msgstr "工单已%s"
#: tickets/api/request_asset_perm.py:91
#: tickets/api/request_asset_perm.py:93
msgid "Confirm assets first"
msgstr "请先确认资产"
#: tickets/api/request_asset_perm.py:94
#: tickets/api/request_asset_perm.py:96
msgid "Confirmed assets changed"
msgstr "确认的资产变更了"
#: tickets/api/request_asset_perm.py:98
#: tickets/api/request_asset_perm.py:100
msgid "Confirm system-user first"
msgstr "请先确认系统用户"
#: tickets/api/request_asset_perm.py:102
#: tickets/api/request_asset_perm.py:104
msgid "Confirmed system-user changed"
msgstr "确认的系统用户变更了"
#: tickets/api/request_asset_perm.py:105 tickets/api/request_asset_perm.py:112
#: xpack/plugins/cloud/models.py:202
#: tickets/api/request_asset_perm.py:107 tickets/api/request_asset_perm.py:114
#: xpack/plugins/cloud/models.py:211
msgid "Succeed"
msgstr "成功"
#: tickets/api/request_asset_perm.py:120
#: tickets/api/request_asset_perm.py:122
msgid "From request ticket: {} {}"
msgstr "来自工单申请: {} {}"
#: tickets/api/request_asset_perm.py:122
#: tickets/api/request_asset_perm.py:124
msgid "{} request assets, approved by {}"
msgstr "{} 申请资产,通过人 {}"
@@ -2686,11 +2685,11 @@ msgstr ""
" 过期时间: {date_expired}<br>\n"
" "
#: tickets/utils.py:20
#: tickets/utils.py:21
msgid "New ticket"
msgstr "新工单"
#: tickets/utils.py:28
#: tickets/utils.py:25
#, python-brace-format
msgid ""
"\n"
@@ -2715,11 +2714,11 @@ msgstr ""
" </div>\n"
" "
#: tickets/utils.py:47
#: tickets/utils.py:44
msgid "Ticket has been reply"
msgstr "工单已被回复"
#: tickets/utils.py:48
#: tickets/utils.py:45
#, python-brace-format
msgid ""
"\n"
@@ -2750,7 +2749,7 @@ msgstr ""
" </div>\n"
" "
#: users/api/user.py:158
#: users/api/user.py:156
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
@@ -2796,7 +2795,7 @@ msgstr "确认密码"
msgid "Password does not match"
msgstr "密码不一致"
#: users/forms/profile.py:89 users/models/user.py:493
#: users/forms/profile.py:89 users/models/user.py:503
#: users/templates/users/user_detail.html:57
#: users/templates/users/user_profile.html:59
msgid "Email"
@@ -2832,12 +2831,12 @@ msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
#: users/forms/profile.py:137 users/forms/user.py:90
#: users/serializers/user.py:200 users/serializers/user.py:282
#: users/serializers/user.py:340
#: users/serializers/user.py:189 users/serializers/user.py:271
#: users/serializers/user.py:329
msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法"
#: users/forms/user.py:31 users/models/user.py:536
#: users/forms/user.py:31 users/models/user.py:546
#: users/templates/users/user_detail.html:89
#: users/templates/users/user_list.html:18
#: users/templates/users/user_profile.html:102
@@ -2875,7 +2874,7 @@ msgstr "密码策略"
msgid "System administrator"
msgstr "系统管理员"
#: users/models/user.py:159
#: users/models/user.py:158
msgid "System auditor"
msgstr "系统审计员"
@@ -2883,83 +2882,83 @@ msgstr "系统审计员"
msgid "Application"
msgstr "应用程序"
#: users/models/user.py:413 users/templates/users/user_profile.html:90
#: users/models/user.py:423 users/templates/users/user_profile.html:90
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:480
#: users/models/user.py:490
msgid "Local"
msgstr "数据库"
#: users/models/user.py:504
#: users/models/user.py:514
msgid "Avatar"
msgstr "头像"
#: users/models/user.py:507 users/templates/users/user_detail.html:68
#: users/models/user.py:517 users/templates/users/user_detail.html:68
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:540
#: users/models/user.py:550
msgid "Date password last updated"
msgstr "最后更新密码日期"
#: users/models/user.py:653
#: users/models/user.py:657
msgid "Administrator"
msgstr "管理员"
#: users/models/user.py:656
#: users/models/user.py:660
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
#: users/serializers/user.py:53 users/serializers/user.py:88
#: users/serializers/user.py:50 users/serializers/user.py:84
msgid "Organization role name"
msgstr "组织角色名称"
#: users/serializers/user.py:55
msgid "Total role name"
msgstr "汇总角色名称"
#: users/serializers/user.py:79 users/serializers/user.py:253
#: users/serializers/user.py:75 users/serializers/user.py:242
msgid "Is first login"
msgstr "首次登录"
#: users/serializers/user.py:80
#: users/serializers/user.py:76
msgid "Is valid"
msgstr "账户是否有效"
#: users/serializers/user.py:81
#: users/serializers/user.py:77
msgid "Is expired"
msgstr " 是否过期"
#: users/serializers/user.py:82
#: users/serializers/user.py:78
msgid "Avatar url"
msgstr "头像路径"
#: users/serializers/user.py:86
#: users/serializers/user.py:82
msgid "Groups name"
msgstr "用户组名"
#: users/serializers/user.py:87
#: users/serializers/user.py:83
msgid "Source name"
msgstr "用户来源名"
#: users/serializers/user.py:89
#: users/serializers/user.py:85
msgid "Super role name"
msgstr "超级角色名称"
#: users/serializers/user.py:120
#: users/serializers/user.py:86
msgid "Total role name"
msgstr "汇总角色名称"
#: users/serializers/user.py:109
msgid "Role limit to {}"
msgstr "角色只能为 {}"
#: users/serializers/user.py:132 users/serializers/user.py:306
#: users/serializers/user.py:121 users/serializers/user.py:295
msgid "Password does not match security rules"
msgstr "密码不满足安全规则"
#: users/serializers/user.py:298
#: users/serializers/user.py:287
msgid "The old password is incorrect"
msgstr "旧密码错误"
#: users/serializers/user.py:312
#: users/serializers/user.py:301
msgid "The newly set password is inconsistent"
msgstr "两次密码不一致"
@@ -2973,7 +2972,7 @@ msgstr "安全令牌验证"
#: users/templates/users/_base_otp.html:14 users/templates/users/_user.html:13
#: users/templates/users/user_profile_update.html:55
#: xpack/plugins/cloud/models.py:119 xpack/plugins/cloud/serializers.py:82
#: xpack/plugins/cloud/models.py:124 xpack/plugins/cloud/serializers.py:83
msgid "Account"
msgstr "账户"
@@ -3137,7 +3136,7 @@ msgstr "很强"
#: users/templates/users/user_database_app_permission.html:41
#: users/templates/users/user_list.html:19
#: users/templates/users/user_remote_app_permission.html:41
#: xpack/plugins/cloud/models.py:50
#: xpack/plugins/cloud/models.py:51
msgid "Validity"
msgstr "有效"
@@ -3836,79 +3835,95 @@ msgstr "无法将数据发送到远程"
msgid "Cloud center"
msgstr "云管中心"
#: xpack/plugins/cloud/models.py:29
#: xpack/plugins/cloud/models.py:30
msgid "Available"
msgstr "有效"
#: xpack/plugins/cloud/models.py:30
#: xpack/plugins/cloud/models.py:31
msgid "Unavailable"
msgstr "无效"
#: xpack/plugins/cloud/models.py:39
#: xpack/plugins/cloud/models.py:40
msgid "Provider"
msgstr "云服务商"
#: xpack/plugins/cloud/models.py:42
#: xpack/plugins/cloud/models.py:43
msgid "Access key id"
msgstr ""
#: xpack/plugins/cloud/models.py:46
#: xpack/plugins/cloud/models.py:47
msgid "Access key secret"
msgstr ""
#: xpack/plugins/cloud/models.py:64
#: xpack/plugins/cloud/models.py:65
msgid "Cloud account"
msgstr "云账号"
#: xpack/plugins/cloud/models.py:122 xpack/plugins/cloud/serializers.py:59
#: xpack/plugins/cloud/models.py:120
msgid "Instance name"
msgstr "实例名称"
#: xpack/plugins/cloud/models.py:121
msgid "Instance name and Partial IP"
msgstr "实例名称和部分IP"
#: xpack/plugins/cloud/models.py:127 xpack/plugins/cloud/serializers.py:59
msgid "Regions"
msgstr "地域"
#: xpack/plugins/cloud/models.py:125
#: xpack/plugins/cloud/models.py:130
msgid "Instances"
msgstr "实例"
#: xpack/plugins/cloud/models.py:137 xpack/plugins/cloud/serializers.py:86
#: xpack/plugins/cloud/models.py:134
msgid "Hostname strategy"
msgstr "主机名策略"
#: xpack/plugins/cloud/models.py:146 xpack/plugins/cloud/serializers.py:87
msgid "Always update"
msgstr "总是更新"
#: xpack/plugins/cloud/models.py:143
#: xpack/plugins/cloud/models.py:152
msgid "Date last sync"
msgstr "最后同步日期"
#: xpack/plugins/cloud/models.py:154 xpack/plugins/cloud/models.py:207
#: xpack/plugins/cloud/models.py:163 xpack/plugins/cloud/models.py:216
msgid "Sync instance task"
msgstr "同步实例任务"
#: xpack/plugins/cloud/models.py:217 xpack/plugins/cloud/models.py:272
#: xpack/plugins/cloud/models.py:226 xpack/plugins/cloud/models.py:281
msgid "Date sync"
msgstr "同步日期"
#: xpack/plugins/cloud/models.py:245
#: xpack/plugins/cloud/models.py:254
msgid "Unsync"
msgstr "未同步"
#: xpack/plugins/cloud/models.py:246 xpack/plugins/cloud/models.py:247
#: xpack/plugins/cloud/models.py:255
msgid "New Sync"
msgstr "新同步"
#: xpack/plugins/cloud/models.py:256
msgid "Synced"
msgstr "已同步"
#: xpack/plugins/cloud/models.py:248
#: xpack/plugins/cloud/models.py:257
msgid "Released"
msgstr "已释放"
#: xpack/plugins/cloud/models.py:253
#: xpack/plugins/cloud/models.py:262
msgid "Sync task"
msgstr "同步任务"
#: xpack/plugins/cloud/models.py:257
#: xpack/plugins/cloud/models.py:266
msgid "Sync instance task history"
msgstr "同步实例任务历史"
#: xpack/plugins/cloud/models.py:260
#: xpack/plugins/cloud/models.py:269
msgid "Instance"
msgstr "实例"
#: xpack/plugins/cloud/models.py:263
#: xpack/plugins/cloud/models.py:272
msgid "Region"
msgstr "地域"
@@ -3992,7 +4007,7 @@ msgstr "执行次数"
msgid "Instance count"
msgstr "实例个数"
#: xpack/plugins/cloud/serializers.py:85
#: xpack/plugins/cloud/serializers.py:86
#: xpack/plugins/gathered_user/serializers.py:20
msgid "Periodic display"
msgstr "定时执行"
@@ -4057,30 +4072,34 @@ msgstr "管理页面logo"
msgid "Logo of logout page"
msgstr "退出页面logo"
#: xpack/plugins/license/api.py:46
#: xpack/plugins/license/api.py:37
msgid "License import successfully"
msgstr "许可证导入成功"
#: xpack/plugins/license/api.py:47
#: xpack/plugins/license/api.py:38
msgid "License is invalid"
msgstr "无效的许可证"
#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94
#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:124
msgid "License"
msgstr "许可证"
#: xpack/plugins/license/models.py:74
#: xpack/plugins/license/models.py:71
msgid "Standard edition"
msgstr "标准版"
#: xpack/plugins/license/models.py:76
#: xpack/plugins/license/models.py:73
msgid "Enterprise edition"
msgstr "企业版"
#: xpack/plugins/license/models.py:78
#: xpack/plugins/license/models.py:75
msgid "Ultimate edition"
msgstr "旗舰版"
#: xpack/plugins/license/models.py:77
msgid "Community edition"
msgstr "社区版"
#~ msgid "Organization User"
#~ msgstr "组织用户"

View File

@@ -1,5 +1,6 @@
import uuid
from functools import partial
from itertools import chain
from django.db import models
from django.db.models import signals
@@ -229,6 +230,16 @@ def _none2list(*args):
return ([] if v is None else v for v in args)
def _users2pks_if_need(users, admins, auditors):
pks = []
for user in chain(users, admins, auditors):
if hasattr(user, 'pk'):
pks.append(user.pk)
else:
pks.append(user)
return pks
class UserRoleMapper(dict):
def __init__(self, container=set):
super().__init__()
@@ -266,7 +277,7 @@ class OrgMemeberManager(models.Manager):
users, admins, auditors = _none2list(users, admins, auditors)
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
model=User, pk_set=[*users, *admins, *auditors], using=self.db)
model=User, pk_set=_users2pks_if_need(users, admins, auditors), using=self.db)
send(action="pre_remove")
self.filter(org_id=org.id).filter(
@@ -290,14 +301,14 @@ class OrgMemeberManager(models.Manager):
)
oms_add = []
for users, role in add_mapper:
for user in users:
if isinstance(user, models.Model):
user = user.id
oms_add.append(self.model(org_id=org.id, user_id=user, role=role))
for _users, _role in add_mapper:
for _user in _users:
if isinstance(_user, models.Model):
_user = _user.id
oms_add.append(self.model(org_id=org.id, user_id=_user, role=_role))
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
model=User, pk_set=[*users, *admins, *auditors], using=self.db)
model=User, pk_set=_users2pks_if_need(users, admins, auditors), using=self.db)
send(action='pre_add')
self.bulk_create(oms_add)

View File

@@ -52,9 +52,9 @@ class OrgReadSerializer(OrgSerializer):
class OrgMemberSerializer(BulkModelSerializer):
org_display = serializers.CharField()
user_display = serializers.CharField()
role_display = serializers.CharField(source='get_role_display')
org_display = serializers.CharField(read_only=True)
user_display = serializers.CharField(read_only=True)
role_display = serializers.CharField(source='get_role_display', read_only=True)
class Meta:
model = OrganizationMember

View File

@@ -5,9 +5,11 @@ from django.db.models.signals import m2m_changed
from django.db.models.signals import post_save
from django.dispatch import receiver
from orgs.utils import tmp_to_org
from .models import Organization, OrganizationMember
from .hands import set_current_org, current_org, Node, get_current_org
from perms.models import AssetPermission
from perms.models import (AssetPermission, DatabaseAppPermission,
K8sAppPermission, RemoteAppPermission)
from users.models import UserGroup
@@ -26,31 +28,47 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
instance.expire_cache()
def _remove_users(model, users, org, reverse=False):
if not isinstance(users, (tuple, list, set)):
users = (users, )
def _remove_users(model, users, org):
with tmp_to_org(org):
if not isinstance(users, (tuple, list, set)):
users = (users, )
m2m_model = model.users.through
if reverse:
m2m_field_name = model.users.field.m2m_reverse_field_name()
else:
m2m_field_name = model.users.field.m2m_field_name()
m2m_model.objects.filter(**{'user__in': users, f'{m2m_field_name}__org_id': org.id}).delete()
m2m_model = model.users.through
if model.users.reverse:
m2m_field_name = model.users.field.m2m_reverse_field_name()
else:
m2m_field_name = model.users.field.m2m_field_name()
m2m_model.objects.filter(**{'user__in': users, f'{m2m_field_name}__org_id': org.id}).delete()
def _clear_users_from_org(org, users):
"""
清理用户在该组织下的相关数据
"""
if not users:
return
old_org = current_org
set_current_org(org)
_remove_users(AssetPermission, users, org)
_remove_users(UserGroup, users, org, reverse=True)
set_current_org(old_org)
models = (AssetPermission, DatabaseAppPermission,
RemoteAppPermission, K8sAppPermission, UserGroup)
for m in models:
_remove_users(m, users, org)
@receiver(m2m_changed, sender=OrganizationMember)
def on_org_user_changed(sender, instance=None, action=None, pk_set=None, **kwargs):
def on_org_user_changed(action, instance, reverse, pk_set, **kwargs):
if action == 'post_remove':
leaved_users = set(pk_set) - set(instance.members.values_list('id', flat=True))
_clear_users_from_org(instance, leaved_users)
if reverse:
user = instance
org_pk_set = pk_set
orgs = Organization.objects.filter(id__in=org_pk_set)
for org in orgs:
if not org.members.filter(id=user.id).exists():
_clear_users_from_org(org, user)
else:
org = instance
user_pk_set = pk_set
leaved_users = set(pk_set) - set(org.members.filter(id__in=user_pk_set).values_list('id', flat=True))
_clear_users_from_org(org, leaved_users)

View File

@@ -2,6 +2,7 @@
#
import json
import threading
from collections.abc import Iterable
from smtplib import SMTPSenderRefused
from rest_framework import generics
@@ -15,7 +16,7 @@ from .utils import (
LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil,
LDAP_USE_CACHE_FLAGS, LDAPTestUtil, ObjectDict
)
from .tasks import sync_ldap_user_task
from .tasks import sync_ldap_user
from common.permissions import IsOrgAdmin, IsSuperUser
from common.utils import get_logger
from .serializers import (
@@ -204,8 +205,9 @@ class LDAPUserListApi(generics.ListAPIView):
if sync_util.task_no_start:
# 任务外部设置 task running 状态
sync_util.set_task_status(sync_util.TASK_STATUS_IS_RUNNING)
task = sync_ldap_user_task.delay()
data = {'msg': 'Cache no data, sync task {} started.'.format(task.id)}
t = threading.Thread(target=sync_ldap_user)
t.start()
data = {'msg': 'Sync start.'}
return Response(data=data, status=409)
# 同步任务正在执行
if sync_util.task_is_running:
@@ -214,7 +216,7 @@ class LDAPUserListApi(generics.ListAPIView):
# 同步任务执行结束
if sync_util.task_is_over:
msg = sync_util.get_task_error_msg()
data = {'error': 'Synchronization task report error: {}'.format(msg)}
data = {'error': 'Synchronization error: {}'.format(msg)}
return Response(data=data, status=400)
return super().list(request, *args, **kwargs)
@@ -277,6 +279,7 @@ class PublicSettingApi(generics.RetrieveAPIView):
"SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL,
"SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION,
"LOGO_URLS": settings.LOGO_URLS,
"TICKETS_ENABLED": settings.TICKETS_ENABLED,
"PASSWORD_RULE": {
'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH,
'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE,

View File

@@ -1,17 +1,14 @@
# coding: utf-8
#
from celery import shared_task
from common.utils import get_logger
from ..utils import LDAPSyncUtil
__all__ = ['sync_ldap_user_task']
__all__ = ['sync_ldap_user']
logger = get_logger(__file__)
@shared_task
def sync_ldap_user_task():
def sync_ldap_user():
LDAPSyncUtil().perform_sync()

View File

@@ -1,8 +1,10 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from common.drf.serializers import BulkModelSerializer, AdaptedBulkListSerializer
from common.utils import is_uuid
from ..models import (
Terminal, Status, Session, Task
Terminal, Status, Session, Task, CommandStorage, ReplayStorage
)
@@ -18,6 +20,31 @@ class TerminalSerializer(BulkModelSerializer):
'is_alive', 'date_created', 'command_storage', 'replay_storage'
]
@staticmethod
def get_kwargs_may_be_uuid(value):
kwargs = {}
if is_uuid(value):
kwargs['id'] = value
else:
kwargs['name'] = value
return kwargs
def validate_command_storage(self, value):
kwargs = self.get_kwargs_may_be_uuid(value)
storage = CommandStorage.objects.filter(**kwargs).first()
if storage:
return storage.name
else:
raise serializers.ValidationError(_('Not found'))
def validate_replay_storage(self, value):
kwargs = self.get_kwargs_may_be_uuid(value)
storage = ReplayStorage.objects.filter(**kwargs).first()
if storage:
return storage.name
else:
raise serializers.ValidationError(_('Not found'))
@staticmethod
def get_session_online(obj):
return Session.objects.filter(terminal=obj, is_finished=False).count()

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.2.13 on 2020-09-07 11:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tickets', '0003_auto_20200804_1551'),
]
operations = [
migrations.AddField(
model_name='ticket',
name='comment',
field=models.TextField(blank=True, default='', max_length=128, verbose_name='Comment'),
),
]

View File

@@ -40,6 +40,7 @@ class Ticket(OrgModelMixin, CommonModelMixin):
type = models.CharField(max_length=16, choices=TYPE.choices, default=TYPE.GENERAL, verbose_name=_("Type"))
status = models.CharField(choices=STATUS.choices, max_length=16, default='open')
action = models.CharField(choices=ACTION.choices, max_length=16, default='', blank=True)
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
origin_objects = models.Manager()
@@ -78,7 +79,6 @@ class Ticket(OrgModelMixin, CommonModelMixin):
)
self.status = status
self.assignee = user
self.assignees_display = str(user)
self.save()
def create_comment(self, action_display, user, extra_comment=None):
@@ -96,7 +96,6 @@ class Ticket(OrgModelMixin, CommonModelMixin):
self.action = action
self.status = self.STATUS.CLOSED
self.assignee = user
self.assignees_display = str(user)
self.save()
def is_assignee(self, user):

View File

@@ -46,7 +46,7 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
'status', 'action', 'date_created', 'date_updated', 'system_user_waitlist_url',
'type', 'type_display', 'action_display', 'ips', 'confirmed_assets',
'date_start', 'date_expired', 'confirmed_system_user', 'hostname',
'assets_waitlist_url', 'system_user', 'org_id', 'actions'
'assets_waitlist_url', 'system_user', 'org_id', 'actions', 'comment'
]
m2m_fields = [
'user', 'user_display', 'assignees', 'assignees_display',

View File

@@ -44,9 +44,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
def get_queryset(self):
return super().get_queryset().annotate(
gc_m2m_org_members__role=GroupConcat('m2m_org_members__role'),
gc_groups__name=GroupConcat('groups__name'),
gc_groups=GroupConcat('groups__id', output_field=CharField())
)
).prefetch_related('groups')
def send_created_signal(self, users):
if not isinstance(users, list):

View File

@@ -560,12 +560,6 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
@property
def groups_display(self):
if hasattr(self, 'gc_groups__name'):
names = self.gc_groups__name
if isinstance(names, str):
return ' '.join(set(self.gc_groups__name.split(',')))
else:
return ''
return ' '.join([group.name for group in self.groups.all()])
@property

View File

@@ -44,9 +44,6 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
label=_('Password strategy'), write_only=True
)
mfa_level_display = serializers.ReadOnlyField(source='get_mfa_level_display')
groups = GroupConcatedPrimaryKeyRelatedField(
label=_('User group'), many=True, queryset=UserGroup.objects.all(), required=False
)
login_blocked = serializers.SerializerMethodField()
can_update = serializers.SerializerMethodField()
can_delete = serializers.SerializerMethodField()

View File

@@ -116,7 +116,10 @@ REDIS_PORT: 6379
# Perm show single asset to ungrouped node
# 是否把未授权节点资产放入到 未分组 节点中
# PERM_SINGLE_ASSET_TO_UNGROUP_NODE: false
# PERM_SINGLE_ASSET_TO_UNGROUP_NODE: False
#
# 同一账号仅允许在一台设备登录
# USER_LOGIN_SINGLE_MACHINE_ENABLED: False
#
# 启用定时任务
# PERIOD_TASK_ENABLE: True