diff --git a/Dockerfile b/Dockerfile index 7cf768d85..9c0cd9dbf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/README.md b/README.md index 326b50260..30befbf73 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,12 @@ 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) > 注:企业版支持离线安装,申请通过后会提供高速下载链接。 diff --git a/apps/audits/tasks.py b/apps/audits/tasks.py index 2dfe4a2dd..d928e6e87 100644 --- a/apps/audits/tasks.py +++ b/apps/audits/tasks.py @@ -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() diff --git a/apps/authentication/signals_handlers.py b/apps/authentication/signals_handlers.py index 461ddbb99..dc6bf5e27 100644 --- a/apps/authentication/signals_handlers.py +++ b/apps/authentication/signals_handlers.py @@ -1,3 +1,8 @@ +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 jms_oidc_rp.signals import openid_user_login_failed, openid_user_login_success @@ -5,6 +10,17 @@ from jms_oidc_rp.signals import openid_user_login_failed, openid_user_login_succ 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) diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py index 945a98ea4..b4ac41b2f 100644 --- a/apps/common/fields/model.py +++ b/apps/common/fields/model.py @@ -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): diff --git a/apps/common/tasks.py b/apps/common/tasks.py index aecc54ca7..eeeee7214 100644 --- a/apps/common/tasks.py +++ b/apps/common/tasks.py @@ -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) diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py index ea3590d6c..31991c93c 100644 --- a/apps/common/utils/crypto.py +++ b/apps/common/utils/crypto.py @@ -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') diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index dcbf439da..0e186726e 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -266,7 +266,8 @@ 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 } def compatible_auth_openid_of_key(self): diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index b691d3ce5..5149c3d9c 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -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 diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index e0e23c4e4..0a5f32daf 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ec532c0b6..3b5624384 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: JumpServer team\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 {} confirm, You also can copy link to her/him
\n" " Don't close this page" @@ -1394,19 +1394,19 @@ msgstr "" "等待 {} 确认, 你也可以复制链接发给他/她
\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 "" "
Luna是单独部署的一个程序,你需要部署luna,koko,
如果你看到了" "这个页面,证明你访问的不是nginx监听的端口,祝你好运
" -#: 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 "" "
Koko is a separately deployed program, you need to deploy Koko, " "configure nginx for url distribution,
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}
\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 "" " \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 "" " \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 "组织用户" diff --git a/apps/settings/api.py b/apps/settings/api.py index 786c43bad..2c7c31d13 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -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) diff --git a/apps/settings/tasks/ldap.py b/apps/settings/tasks/ldap.py index 60058e03e..6869d0254 100644 --- a/apps/settings/tasks/ldap.py +++ b/apps/settings/tasks/ldap.py @@ -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() diff --git a/apps/tickets/migrations/0004_ticket_comment.py b/apps/tickets/migrations/0004_ticket_comment.py new file mode 100644 index 000000000..c30776eb9 --- /dev/null +++ b/apps/tickets/migrations/0004_ticket_comment.py @@ -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'), + ), + ] diff --git a/apps/tickets/models/ticket.py b/apps/tickets/models/ticket.py index 3e979f244..f52172fb3 100644 --- a/apps/tickets/models/ticket.py +++ b/apps/tickets/models/ticket.py @@ -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() diff --git a/apps/tickets/serializers/request_asset_perm.py b/apps/tickets/serializers/request_asset_perm.py index 8827f482c..5c7e08cfd 100644 --- a/apps/tickets/serializers/request_asset_perm.py +++ b/apps/tickets/serializers/request_asset_perm.py @@ -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', diff --git a/apps/users/api/user.py b/apps/users/api/user.py index ae4550931..148378522 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -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): diff --git a/apps/users/models/user.py b/apps/users/models/user.py index f65b9398e..f0f2ec896 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -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 diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index d6968a2d8..ccb269350 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -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() diff --git a/config_example.yml b/config_example.yml index fd8710c78..f23b5c7e5 100644 --- a/config_example.yml +++ b/config_example.yml @@ -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