Compare commits

...

44 Commits

Author SHA1 Message Date
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
33 changed files with 399 additions and 196 deletions

View File

@@ -21,7 +21,7 @@ RUN useradd jumpserver
RUN yum -y install epel-release && \ RUN yum -y install epel-release && \
echo -e "[mysql]\nname=mysql\nbaseurl=${MYSQL_MIRROR}\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo 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 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} pip config set global.index-url ${PIP_MIRROR}
RUN pip install -r requirements/requirements.txt || pip install -r requirements/requirements.txt 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/) [![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) [![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 是全球首款开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 规范的运维安全审计系统。
JumpServer 使用 Python / Django 为主进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。 JumpServer 使用 Python / Django 为主进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。
@@ -202,6 +206,16 @@ v2.1.0 是 v2.0.0 之后的功能版本。
- [完整文档](https://docs.jumpserver.org) - [完整文档](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) - [演示视频](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) - [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)

View File

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

View File

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

View File

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

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 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 radiusauth.backends import RADIUSBackend, RADIUSRealmBackend
from django.conf import settings from django.conf import settings
@@ -38,16 +38,12 @@ class CreateUserMixin:
return [], False, False return [], False, False
return None 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): class RadiusBackend(CreateUserMixin, RADIUSBackend):
pass def authenticate(self, request, username='', password='', **kwargs):
return super().authenticate(request, username=username, password=password)
class RadiusRealmBackend(CreateUserMixin, RADIUSRealmBackend): 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): def create_confirm_ticket(self, request=None):
from tickets.models import Ticket from tickets.models import Ticket
title = _('Login confirm') + '{}'.format(self.user) title = _('Login confirm') + ' {}'.format(self.user)
if request: if request:
remote_addr = get_request_ip(request) remote_addr = get_request_ip(request)
city = get_ip_city(remote_addr) 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.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 jms_oidc_rp.signals import openid_user_login_failed, openid_user_login_success
from .signals import post_auth_success, post_auth_failed 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) @receiver(openid_user_login_success)
def on_oidc_user_login_success(sender, request, user, **kwargs): def on_oidc_user_login_success(sender, request, user, **kwargs):
post_auth_success.send(sender, user=user, request=request) 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) @receiver(openid_user_login_failed)
def on_oidc_user_login_failed(sender, username, request, reason, **kwargs): def on_oidc_user_login_failed(sender, username, request, reason, **kwargs):
post_auth_failed.send(sender, username=username, request=request, reason=reason) 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 %} {% endif %}
</div> </div>
<div class="form-group"> <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 %} {% if form.errors.password %}
<div class="help-block field-error"> <div class="help-block field-error">
<p class="red-fonts">{{ form.errors.password.as_text }}</p> <p class="red-fonts">{{ form.errors.password.as_text }}</p>
@@ -86,7 +87,7 @@
var rsaPublicKey = "{{ rsa_public_key }}" var rsaPublicKey = "{{ rsa_public_key }}"
var password =$('#password').val(); //明文密码 var password =$('#password').val(); //明文密码
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey) var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
$('#password').val(passwordEncrypted); //返回给密码输入input $('#password-hidden').val(passwordEncrypted); //返回给密码输入input
$('#form').submit();//post提交 $('#form').submit();//post提交
} }
</script> </script>

View File

@@ -106,7 +106,8 @@
{% endif %} {% endif %}
</div> </div>
<div class="form-group"> <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 %} {% if form.errors.password %}
<div class="help-block field-error"> <div class="help-block field-error">
<p class="red-fonts">{{ form.errors.password.as_text }}</p> <p class="red-fonts">{{ form.errors.password.as_text }}</p>
@@ -157,7 +158,7 @@
var rsaPublicKey = "{{ rsa_public_key }}" var rsaPublicKey = "{{ rsa_public_key }}"
var password =$('#password').val(); //明文密码 var password =$('#password').val(); //明文密码
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey) var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
$('#password').val(passwordEncrypted); //返回给密码输入input $('#password-hidden').val(passwordEncrypted); //返回给密码输入input
$('#contact-form').submit();//post提交 $('#contact-form').submit();//post提交
} }
</script> </script>

View File

@@ -17,6 +17,7 @@ from django.views.generic.base import TemplateView, RedirectView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django.conf import settings from django.conf import settings
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.contrib.auth import BACKEND_SESSION_KEY
from common.const.front_urls import TICKET_DETAIL from common.const.front_urls import TICKET_DETAIL
from common.utils import get_request_ip, get_object_or_none from common.utils import get_request_ip, get_object_or_none
@@ -205,12 +206,12 @@ class UserLoginWaitConfirmView(TemplateView):
class UserLogoutView(TemplateView): class UserLogoutView(TemplateView):
template_name = 'flash_message_standalone.html' template_name = 'flash_message_standalone.html'
@staticmethod def get_backend_logout_url(self):
def get_backend_logout_url(): backend = self.request.session.get(BACKEND_SESSION_KEY, '')
if settings.AUTH_OPENID: if 'OIDC' in backend:
return settings.AUTH_OPENID_AUTH_LOGOUT_URL_NAME return settings.AUTH_OPENID_AUTH_LOGOUT_URL_NAME
# if settings.AUTH_CAS: elif 'CAS' in backend:
# return settings.CAS_LOGOUT_URL_NAME return settings.CAS_LOGOUT_URL_NAME
return None return None
def get(self, request, *args, **kwargs): 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.translation import ugettext_lazy as _
from django.utils.encoding import force_text from django.utils.encoding import force_text
from ..utils import signer, aes_crypto from ..utils import signer, aes_crypto, aes_ecb_crypto
__all__ = [ __all__ = [
@@ -117,9 +117,17 @@ class EncryptMixin:
return signer.unsign(value) or '' return signer.unsign(value) or ''
def decrypt_from_aes(self, value): def decrypt_from_aes(self, value):
"""
先尝试使用GCM模式解密如果解不开再尝试使用原来的ECB模式解密
"""
try: try:
return aes_crypto.decrypt(value) return aes_crypto.decrypt(value)
except (TypeError, ValueError): except ValueError:
pass
try:
return aes_ecb_crypto.decrypt(value)
except (TypeError, ValueError, UnicodeDecodeError):
pass pass
def from_db_value(self, value, expression, connection, context): 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: if len(args) == 3:
args = list(args) 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 email_from = settings.EMAIL_FROM or settings.EMAIL_HOST_USER
args.insert(2, email_from) args.insert(2, email_from)
args = tuple(args) args = tuple(args)

View File

@@ -1,5 +1,7 @@
import base64 import base64
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
from django.conf import settings 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")) # 解密 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: if key is None:
key = settings.SECRET_KEY key = settings.SECRET_KEY
a = AESCrypto(key) if mode == 'ECB':
a = AESCrypto(key)
elif mode == 'GCM':
a = AESCryptoGCM(key)
return a 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_SEARCH_FILTER': '(cn=%(user)s)',
'AUTH_LDAP_START_TLS': False, 'AUTH_LDAP_START_TLS': False,
'AUTH_LDAP_USER_ATTR_MAP': {"username": "cn", "name": "sn", "email": "mail"}, '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_SEARCH_PAGED_SIZE': 1000,
'AUTH_LDAP_SYNC_IS_PERIODIC': False, 'AUTH_LDAP_SYNC_IS_PERIODIC': False,
'AUTH_LDAP_SYNC_INTERVAL': None, 'AUTH_LDAP_SYNC_INTERVAL': None,
@@ -266,7 +266,9 @@ class Config(dict):
'ORG_CHANGE_TO_URL': '', 'ORG_CHANGE_TO_URL': '',
'LANGUAGE_CODE': 'zh', 'LANGUAGE_CODE': 'zh',
'TIME_ZONE': 'Asia/Shanghai', '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): 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_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
# ) # )
AUTH_LDAP_CONNECTION_OPTIONS = { 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_CACHE_TIMEOUT = 1
AUTH_LDAP_ALWAYS_UPDATE_USER = True 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_LOGOUT_URL_NAME = "authentication:cas:cas-logout"
CAS_LOGIN_MSG = None CAS_LOGIN_MSG = None
CAS_LOGGED_MSG = None CAS_LOGGED_MSG = None
CAS_IGNORE_REFERER = True
CAS_LOGOUT_COMPLETELY = CONFIG.CAS_LOGOUT_COMPLETELY CAS_LOGOUT_COMPLETELY = CONFIG.CAS_LOGOUT_COMPLETELY
CAS_VERSION = CONFIG.CAS_VERSION CAS_VERSION = CONFIG.CAS_VERSION
CAS_ROOT_PROXIED_AS = CONFIG.CAS_ROOT_PROXIED_AS CAS_ROOT_PROXIED_AS = CONFIG.CAS_ROOT_PROXIED_AS

View File

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

View File

@@ -1,5 +1,6 @@
import uuid import uuid
from functools import partial from functools import partial
from itertools import chain
from django.db import models from django.db import models
from django.db.models import signals from django.db.models import signals
@@ -229,6 +230,10 @@ def _none2list(*args):
return ([] if v is None else v for v in args) return ([] if v is None else v for v in args)
def _users2pks(users, admins, auditors):
return [user.pk for user in chain(users, admins, auditors)]
class UserRoleMapper(dict): class UserRoleMapper(dict):
def __init__(self, container=set): def __init__(self, container=set):
super().__init__() super().__init__()
@@ -266,7 +271,7 @@ class OrgMemeberManager(models.Manager):
users, admins, auditors = _none2list(users, admins, auditors) users, admins, auditors = _none2list(users, admins, auditors)
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, 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(users, admins, auditors), using=self.db)
send(action="pre_remove") send(action="pre_remove")
self.filter(org_id=org.id).filter( self.filter(org_id=org.id).filter(
@@ -297,7 +302,7 @@ class OrgMemeberManager(models.Manager):
oms_add.append(self.model(org_id=org.id, user_id=user, role=role)) 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, 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(users, admins, auditors), using=self.db)
send(action='pre_add') send(action='pre_add')
self.bulk_create(oms_add) self.bulk_create(oms_add)

View File

@@ -52,9 +52,9 @@ class OrgReadSerializer(OrgSerializer):
class OrgMemberSerializer(BulkModelSerializer): class OrgMemberSerializer(BulkModelSerializer):
org_display = serializers.CharField() org_display = serializers.CharField(read_only=True)
user_display = serializers.CharField() user_display = serializers.CharField(read_only=True)
role_display = serializers.CharField(source='get_role_display') role_display = serializers.CharField(source='get_role_display', read_only=True)
class Meta: class Meta:
model = OrganizationMember 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.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from orgs.utils import tmp_to_org
from .models import Organization, OrganizationMember from .models import Organization, OrganizationMember
from .hands import set_current_org, current_org, Node, get_current_org 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 from users.models import UserGroup
@@ -26,31 +28,47 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
instance.expire_cache() instance.expire_cache()
def _remove_users(model, users, org, reverse=False): def _remove_users(model, users, org):
if not isinstance(users, (tuple, list, set)): with tmp_to_org(org):
users = (users, ) if not isinstance(users, (tuple, list, set)):
users = (users, )
m2m_model = model.users.through m2m_model = model.users.through
if reverse: if model.users.reverse:
m2m_field_name = model.users.field.m2m_reverse_field_name() m2m_field_name = model.users.field.m2m_reverse_field_name()
else: else:
m2m_field_name = model.users.field.m2m_field_name() 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.objects.filter(**{'user__in': users, f'{m2m_field_name}__org_id': org.id}).delete()
def _clear_users_from_org(org, users): def _clear_users_from_org(org, users):
"""
清理用户在该组织下的相关数据
"""
if not users: if not users:
return return
old_org = current_org models = (AssetPermission, DatabaseAppPermission,
set_current_org(org) RemoteAppPermission, K8sAppPermission, UserGroup)
_remove_users(AssetPermission, users, org)
_remove_users(UserGroup, users, org, reverse=True) for m in models:
set_current_org(old_org) _remove_users(m, users, org)
@receiver(m2m_changed, sender=OrganizationMember) @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': if action == 'post_remove':
leaved_users = set(pk_set) - set(instance.members.values_list('id', flat=True)) if reverse:
_clear_users_from_org(instance, leaved_users) 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 json
import threading
from collections.abc import Iterable from collections.abc import Iterable
from smtplib import SMTPSenderRefused from smtplib import SMTPSenderRefused
from rest_framework import generics from rest_framework import generics
@@ -15,7 +16,7 @@ from .utils import (
LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil, LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil,
LDAP_USE_CACHE_FLAGS, LDAPTestUtil, ObjectDict 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.permissions import IsOrgAdmin, IsSuperUser
from common.utils import get_logger from common.utils import get_logger
from .serializers import ( from .serializers import (
@@ -204,8 +205,9 @@ class LDAPUserListApi(generics.ListAPIView):
if sync_util.task_no_start: if sync_util.task_no_start:
# 任务外部设置 task running 状态 # 任务外部设置 task running 状态
sync_util.set_task_status(sync_util.TASK_STATUS_IS_RUNNING) sync_util.set_task_status(sync_util.TASK_STATUS_IS_RUNNING)
task = sync_ldap_user_task.delay() t = threading.Thread(target=sync_ldap_user)
data = {'msg': 'Cache no data, sync task {} started.'.format(task.id)} t.start()
data = {'msg': 'Sync start.'}
return Response(data=data, status=409) return Response(data=data, status=409)
# 同步任务正在执行 # 同步任务正在执行
if sync_util.task_is_running: if sync_util.task_is_running:
@@ -214,7 +216,7 @@ class LDAPUserListApi(generics.ListAPIView):
# 同步任务执行结束 # 同步任务执行结束
if sync_util.task_is_over: if sync_util.task_is_over:
msg = sync_util.get_task_error_msg() 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 Response(data=data, status=400)
return super().list(request, *args, **kwargs) return super().list(request, *args, **kwargs)
@@ -277,6 +279,7 @@ class PublicSettingApi(generics.RetrieveAPIView):
"SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL, "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL,
"SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION, "SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION,
"LOGO_URLS": settings.LOGO_URLS, "LOGO_URLS": settings.LOGO_URLS,
"TICKETS_ENABLED": settings.TICKETS_ENABLED,
"PASSWORD_RULE": { "PASSWORD_RULE": {
'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH, 'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH,
'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE, 'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE,

View File

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

View File

@@ -1,8 +1,10 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from common.drf.serializers import BulkModelSerializer, AdaptedBulkListSerializer from common.drf.serializers import BulkModelSerializer, AdaptedBulkListSerializer
from common.utils import is_uuid
from ..models import ( 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' '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 @staticmethod
def get_session_online(obj): def get_session_online(obj):
return Session.objects.filter(terminal=obj, is_finished=False).count() 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")) 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') status = models.CharField(choices=STATUS.choices, max_length=16, default='open')
action = models.CharField(choices=ACTION.choices, max_length=16, default='', blank=True) 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() origin_objects = models.Manager()
@@ -78,7 +79,6 @@ class Ticket(OrgModelMixin, CommonModelMixin):
) )
self.status = status self.status = status
self.assignee = user self.assignee = user
self.assignees_display = str(user)
self.save() self.save()
def create_comment(self, action_display, user, extra_comment=None): def create_comment(self, action_display, user, extra_comment=None):
@@ -96,7 +96,6 @@ class Ticket(OrgModelMixin, CommonModelMixin):
self.action = action self.action = action
self.status = self.STATUS.CLOSED self.status = self.STATUS.CLOSED
self.assignee = user self.assignee = user
self.assignees_display = str(user)
self.save() self.save()
def is_assignee(self, user): 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', 'status', 'action', 'date_created', 'date_updated', 'system_user_waitlist_url',
'type', 'type_display', 'action_display', 'ips', 'confirmed_assets', 'type', 'type_display', 'action_display', 'ips', 'confirmed_assets',
'date_start', 'date_expired', 'confirmed_system_user', 'hostname', '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 = [ m2m_fields = [
'user', 'user_display', 'assignees', 'assignees_display', 'user', 'user_display', 'assignees', 'assignees_display',

View File

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

View File

@@ -560,12 +560,6 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
@property @property
def groups_display(self): 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()]) return ' '.join([group.name for group in self.groups.all()])
@property @property

View File

@@ -44,9 +44,6 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
label=_('Password strategy'), write_only=True label=_('Password strategy'), write_only=True
) )
mfa_level_display = serializers.ReadOnlyField(source='get_mfa_level_display') 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() login_blocked = serializers.SerializerMethodField()
can_update = serializers.SerializerMethodField() can_update = serializers.SerializerMethodField()
can_delete = serializers.SerializerMethodField() can_delete = serializers.SerializerMethodField()

View File

@@ -116,7 +116,10 @@ REDIS_PORT: 6379
# Perm show single asset to ungrouped node # 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 # PERIOD_TASK_ENABLE: True