Unverified Commit 09466457 authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #3433 from jumpserver/dev

Dev
parents 96adaca3 f3d2e5d1
...@@ -44,4 +44,4 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView): ...@@ -44,4 +44,4 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView):
if ok: if ok:
return Response("ok") return Response("ok")
else: else:
return Response({"failed": e}, status=404) return Response({"error": e}, status=400)
...@@ -14,11 +14,12 @@ ...@@ -14,11 +14,12 @@
# limitations under the License. # limitations under the License.
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.conf import settings
from rest_framework.response import Response from rest_framework.response import Response
from common.serializers import CeleryTaskSerializer from common.serializers import CeleryTaskSerializer
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsAppUser
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics from orgs.mixins import generics
from ..models import SystemUser, Asset from ..models import SystemUser, Asset
...@@ -69,7 +70,7 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): ...@@ -69,7 +70,7 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
Get system user with asset auth info Get system user with asset auth info
""" """
model = SystemUser model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsAppUser,)
serializer_class = serializers.SystemUserAuthSerializer serializer_class = serializers.SystemUserAuthSerializer
def get_object(self): def get_object(self):
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
import uuid import uuid
import random import random
import re
import paramiko import paramiko
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -63,6 +63,9 @@ class Gateway(AssetUser): ...@@ -63,6 +63,9 @@ class Gateway(AssetUser):
def test_connective(self, local_port=None): def test_connective(self, local_port=None):
if local_port is None: if local_port is None:
local_port = self.port local_port = self.port
if not re.match(r'\w+$', self.password):
return False, _("Password should not contain special characters")
client = paramiko.SSHClient() client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.SSHClient() proxy = paramiko.SSHClient()
......
...@@ -68,7 +68,7 @@ class TreeMixin: ...@@ -68,7 +68,7 @@ class TreeMixin:
@classmethod @classmethod
def refresh_node_assets(cls, t=None): def refresh_node_assets(cls, t=None):
logger.debug("Refresh node tree assets") logger.debug("Refresh node assets")
key = cls.tree_assets_cache_key key = cls.tree_assets_cache_key
ttl = cls.tree_cache_time ttl = cls.tree_cache_time
if not t: if not t:
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<div class="wrapper wrapper-content"> <div class="wrapper wrapper-content">
<div class="row"> <div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px"> <div class="col-lg-3" id="split-left" style="padding-left: 3px;overflow: auto;max-height: 500px">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px"> <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager "> <div class="file-manager ">
......
...@@ -32,8 +32,7 @@ ...@@ -32,8 +32,7 @@
} }
</style> </style>
<div class="ibox treebox float-e-margins" style="overflow:auto;">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px"> <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager" id="tree-node-id"> <div class="file-manager" id="tree-node-id">
<div id="{% block treeID %}nodeTree{% endblock %}" class="ztree"> <div id="{% block treeID %}nodeTree{% endblock %}" class="ztree">
...@@ -306,6 +305,7 @@ function defaultCallback(action) { ...@@ -306,6 +305,7 @@ function defaultCallback(action) {
$(document).ready(function () { $(document).ready(function () {
$('.treebox').css('height', window.innerHeight - 180);
}) })
.on('click', '.btn-show-current-asset', function(){ .on('click', '.btn-show-current-asset', function(){
hideRMenu(); hideRMenu();
...@@ -322,4 +322,4 @@ $(document).ready(function () { ...@@ -322,4 +322,4 @@ $(document).ready(function () {
location.reload(); location.reload();
}) })
</script> </script>
\ No newline at end of file
...@@ -426,13 +426,15 @@ $(document).ready(function(){ ...@@ -426,13 +426,15 @@ $(document).ready(function(){
function success(data) { function success(data) {
url = setUrlParam(the_url, 'spm', data.spm); url = setUrlParam(the_url, 'spm', data.spm);
requestApi({ requestApi({
url:url, url: url,
method:'DELETE', method: 'DELETE',
success:refreshPage, success: function () {
flash_message:false, var msg = "{% trans 'Asset Deleted.' %}";
swal("{% trans 'Asset Delete' %}", msg, "success");
refreshPage();
},
flash_message: false,
}); });
var msg = "{% trans 'Asset Deleted.' %}";
swal("{% trans 'Asset Delete' %}", msg, "success");
} }
function fail() { function fail() {
var msg = "{% trans 'Asset Deleting failed.' %}"; var msg = "{% trans 'Asset Deleting failed.' %}";
...@@ -440,10 +442,11 @@ $(document).ready(function(){ ...@@ -440,10 +442,11 @@ $(document).ready(function(){
} }
requestApi({ requestApi({
url: "{% url 'api-common:resources-cache' %}", url: "{% url 'api-common:resources-cache' %}",
method:'POST', method: 'POST',
body:JSON.stringify(data), body: JSON.stringify(data),
success:success, success: success,
error:fail error: fail,
flash_message: false
}) })
}) })
} }
......
...@@ -139,7 +139,7 @@ $(document).ready(function(){ ...@@ -139,7 +139,7 @@ $(document).ready(function(){
method: "POST", method: "POST",
body: JSON.stringify({'port': parseInt(data.port)}), body: JSON.stringify({'port': parseInt(data.port)}),
success_message: "{% trans 'Can be connected' %}", success_message: "{% trans 'Can be connected' %}",
fail_message: "{% trans 'The connection fails' %}" {#fail_message: "{% trans 'The connection fails' %}"#}
}) })
}); });
</script> </script>
......
...@@ -47,7 +47,7 @@ def on_openid_login_success(sender, user=None, request=None, **kwargs): ...@@ -47,7 +47,7 @@ def on_openid_login_success(sender, user=None, request=None, **kwargs):
@receiver(populate_user) @receiver(populate_user)
def on_ldap_create_user(sender, user, ldap_user, **kwargs): def on_ldap_create_user(sender, user, ldap_user, **kwargs):
if user and user.username != 'admin': if user and user.username not in ['admin']:
user.source = user.SOURCE_LDAP user.source = user.SOURCE_LDAP
user.save() user.save()
......
...@@ -395,6 +395,7 @@ defaults = { ...@@ -395,6 +395,7 @@ defaults = {
'FLOWER_URL': "127.0.0.1:5555", 'FLOWER_URL': "127.0.0.1:5555",
'DEFAULT_ORG_SHOW_ALL_USERS': True, 'DEFAULT_ORG_SHOW_ALL_USERS': True,
'PERIOD_TASK_ENABLED': True, 'PERIOD_TASK_ENABLED': True,
'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False,
} }
......
...@@ -516,7 +516,7 @@ CELERY_TASK_EAGER_PROPAGATES = True ...@@ -516,7 +516,7 @@ CELERY_TASK_EAGER_PROPAGATES = True
CELERY_WORKER_REDIRECT_STDOUTS = True CELERY_WORKER_REDIRECT_STDOUTS = True
CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO" CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO"
# CELERY_WORKER_HIJACK_ROOT_LOGGER = True # CELERY_WORKER_HIJACK_ROOT_LOGGER = True
CELERY_WORKER_MAX_TASKS_PER_CHILD = 40 # CELERY_WORKER_MAX_TASKS_PER_CHILD = 40
CELERY_TASK_SOFT_TIME_LIMIT = 3600 CELERY_TASK_SOFT_TIME_LIMIT = 3600
# Cache use redis # Cache use redis
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -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: 2019-10-25 10:52+0800\n" "POT-Creation-Date: 2019-11-13 16:38+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"
...@@ -83,7 +83,7 @@ msgstr "运行参数" ...@@ -83,7 +83,7 @@ msgstr "运行参数"
#: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:16 #: assets/templates/assets/label_list.html:16
#: assets/templates/assets/system_user_list.html:51 audits/models.py:19 #: assets/templates/assets/system_user_list.html:51 audits/models.py:20
#: audits/templates/audits/ftp_log_list.html:44 #: audits/templates/audits/ftp_log_list.html:44
#: audits/templates/audits/ftp_log_list.html:74 #: audits/templates/audits/ftp_log_list.html:74
#: perms/forms/asset_permission.py:84 perms/models/asset_permission.py:80 #: perms/forms/asset_permission.py:84 perms/models/asset_permission.py:80
...@@ -137,7 +137,7 @@ msgstr "资产" ...@@ -137,7 +137,7 @@ msgstr "资产"
#: perms/templates/perms/remote_app_permission_remote_app.html:53 #: perms/templates/perms/remote_app_permission_remote_app.html:53
#: perms/templates/perms/remote_app_permission_user.html:53 #: perms/templates/perms/remote_app_permission_user.html:53
#: settings/models.py:29 #: settings/models.py:29
#: settings/templates/settings/_ldap_list_users_modal.html:31 #: settings/templates/settings/_ldap_list_users_modal.html:32
#: settings/templates/settings/command_storage_create.html:41 #: settings/templates/settings/command_storage_create.html:41
#: settings/templates/settings/replay_storage_create.html:44 #: settings/templates/settings/replay_storage_create.html:44
#: settings/templates/settings/terminal_setting.html:83 #: settings/templates/settings/terminal_setting.html:83
...@@ -197,7 +197,7 @@ msgstr "参数" ...@@ -197,7 +197,7 @@ msgstr "参数"
#: orgs/models.py:16 perms/models/base.py:54 #: orgs/models.py:16 perms/models/base.py:54
#: perms/templates/perms/asset_permission_detail.html:98 #: perms/templates/perms/asset_permission_detail.html:98
#: perms/templates/perms/remote_app_permission_detail.html:90 #: perms/templates/perms/remote_app_permission_detail.html:90
#: users/models/user.py:414 users/serializers/v1.py:143 #: users/models/user.py:414 users/serializers/group.py:32
#: users/templates/users/user_detail.html:111 #: users/templates/users/user_detail.html:111
#: xpack/plugins/change_auth_plan/models.py:108 #: xpack/plugins/change_auth_plan/models.py:108
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
...@@ -308,7 +308,7 @@ msgstr "远程应用" ...@@ -308,7 +308,7 @@ msgstr "远程应用"
#: settings/templates/settings/security_setting.html:73 #: settings/templates/settings/security_setting.html:73
#: settings/templates/settings/terminal_setting.html:71 #: settings/templates/settings/terminal_setting.html:71
#: terminal/templates/terminal/terminal_update.html:45 #: terminal/templates/terminal/terminal_update.html:45
#: users/templates/users/_user.html:50 #: users/templates/users/_user.html:51
#: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_detail.html:178 #: users/templates/users/user_detail.html:178
#: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_create_update.html:31
...@@ -352,7 +352,7 @@ msgstr "重置" ...@@ -352,7 +352,7 @@ msgstr "重置"
#: terminal/templates/terminal/command_list.html:47 #: terminal/templates/terminal/command_list.html:47
#: terminal/templates/terminal/session_list.html:52 #: terminal/templates/terminal/session_list.html:52
#: terminal/templates/terminal/terminal_update.html:46 #: terminal/templates/terminal/terminal_update.html:46
#: users/templates/users/_user.html:51 #: users/templates/users/_user.html:52
#: users/templates/users/forgot_password.html:42 #: users/templates/users/forgot_password.html:42
#: users/templates/users/user_bulk_update.html:24 #: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:57 #: users/templates/users/user_list.html:57
...@@ -410,7 +410,7 @@ msgstr "详情" ...@@ -410,7 +410,7 @@ msgstr "详情"
#: assets/templates/assets/label_list.html:39 #: assets/templates/assets/label_list.html:39
#: assets/templates/assets/system_user_detail.html:26 #: assets/templates/assets/system_user_detail.html:26
#: assets/templates/assets/system_user_list.html:29 #: assets/templates/assets/system_user_list.html:29
#: assets/templates/assets/system_user_list.html:81 audits/models.py:33 #: assets/templates/assets/system_user_list.html:81 audits/models.py:34
#: perms/templates/perms/asset_permission_detail.html:30 #: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:178 #: perms/templates/perms/asset_permission_list.html:178
#: perms/templates/perms/remote_app_permission_detail.html:30 #: perms/templates/perms/remote_app_permission_detail.html:30
...@@ -454,7 +454,7 @@ msgstr "更新" ...@@ -454,7 +454,7 @@ msgstr "更新"
#: assets/templates/assets/domain_list.html:55 #: assets/templates/assets/domain_list.html:55
#: assets/templates/assets/label_list.html:40 #: assets/templates/assets/label_list.html:40
#: assets/templates/assets/system_user_detail.html:30 #: assets/templates/assets/system_user_detail.html:30
#: assets/templates/assets/system_user_list.html:82 audits/models.py:34 #: assets/templates/assets/system_user_list.html:82 audits/models.py:35
#: authentication/templates/authentication/_access_key_modal.html:65 #: authentication/templates/authentication/_access_key_modal.html:65
#: ops/templates/ops/task_list.html:69 #: ops/templates/ops/task_list.html:69
#: perms/templates/perms/asset_permission_detail.html:34 #: perms/templates/perms/asset_permission_detail.html:34
...@@ -510,7 +510,7 @@ msgstr "创建远程应用" ...@@ -510,7 +510,7 @@ msgstr "创建远程应用"
#: assets/templates/assets/domain_gateway_list.html:73 #: assets/templates/assets/domain_gateway_list.html:73
#: assets/templates/assets/domain_list.html:29 #: assets/templates/assets/domain_list.html:29
#: assets/templates/assets/label_list.html:17 #: assets/templates/assets/label_list.html:17
#: assets/templates/assets/system_user_list.html:56 audits/models.py:38 #: assets/templates/assets/system_user_list.html:56 audits/models.py:39
#: audits/templates/audits/operate_log_list.html:47 #: audits/templates/audits/operate_log_list.html:47
#: audits/templates/audits/operate_log_list.html:73 #: audits/templates/audits/operate_log_list.html:73
#: authentication/templates/authentication/_access_key_modal.html:34 #: authentication/templates/authentication/_access_key_modal.html:34
...@@ -634,7 +634,7 @@ msgid "Domain" ...@@ -634,7 +634,7 @@ msgid "Domain"
msgstr "网域" msgstr "网域"
#: assets/forms/asset.py:69 assets/forms/asset.py:103 assets/forms/asset.py:116 #: assets/forms/asset.py:69 assets/forms/asset.py:103 assets/forms/asset.py:116
#: assets/forms/asset.py:152 assets/models/node.py:421 #: assets/forms/asset.py:152 assets/models/node.py:462
#: assets/serializers/system_user.py:36 #: assets/serializers/system_user.py:36
#: assets/templates/assets/asset_create.html:42 #: assets/templates/assets/asset_create.html:42
#: perms/forms/asset_permission.py:87 perms/forms/asset_permission.py:94 #: perms/forms/asset_permission.py:87 perms/forms/asset_permission.py:94
...@@ -679,9 +679,9 @@ msgstr "选择资产" ...@@ -679,9 +679,9 @@ msgstr "选择资产"
msgid "Content should not be contain: {}" msgid "Content should not be contain: {}"
msgstr "内容不能包含: {}" msgstr "内容不能包含: {}"
#: assets/forms/domain.py:55 #: assets/forms/domain.py:55 assets/models/domain.py:67
msgid "Password should not contain special characters" msgid "Password should not contain special characters"
msgstr "不能包含特殊字符" msgstr "密码不能包含特殊字符"
#: assets/forms/domain.py:74 #: assets/forms/domain.py:74
msgid "SSH gateway support proxy SSH,RDP,VNC" msgid "SSH gateway support proxy SSH,RDP,VNC"
...@@ -696,14 +696,14 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" ...@@ -696,14 +696,14 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: assets/templates/assets/admin_user_list.html:45 #: assets/templates/assets/admin_user_list.html:45
#: assets/templates/assets/domain_gateway_list.html:71 #: assets/templates/assets/domain_gateway_list.html:71
#: assets/templates/assets/system_user_detail.html:62 #: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:48 audits/models.py:80 #: assets/templates/assets/system_user_list.html:48 audits/models.py:81
#: audits/templates/audits/login_log_list.html:57 authentication/forms.py:13 #: audits/templates/audits/login_log_list.html:57 authentication/forms.py:13
#: authentication/templates/authentication/login.html:65 #: authentication/templates/authentication/login.html:65
#: authentication/templates/authentication/new_login.html:92 #: authentication/templates/authentication/new_login.html:92
#: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70 #: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70
#: perms/templates/perms/asset_permission_user.html:55 #: perms/templates/perms/asset_permission_user.html:55
#: perms/templates/perms/remote_app_permission_user.html:54 #: perms/templates/perms/remote_app_permission_user.html:54
#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:13 #: settings/templates/settings/_ldap_list_users_modal.html:31 users/forms.py:14
#: users/models/user.py:371 users/templates/users/_select_user_modal.html:14 #: users/models/user.py:371 users/templates/users/_select_user_modal.html:14
#: users/templates/users/user_detail.html:67 #: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:36 #: users/templates/users/user_list.html:36
...@@ -730,7 +730,7 @@ msgstr "密码或密钥密码" ...@@ -730,7 +730,7 @@ msgstr "密码或密钥密码"
#: authentication/forms.py:15 #: authentication/forms.py:15
#: authentication/templates/authentication/login.html:68 #: authentication/templates/authentication/login.html:68
#: authentication/templates/authentication/new_login.html:95 #: authentication/templates/authentication/new_login.html:95
#: settings/forms.py:114 users/forms.py:15 users/forms.py:27 #: settings/forms.py:114 users/forms.py:16 users/forms.py:42
#: users/templates/users/reset_password.html:53 #: users/templates/users/reset_password.html:53
#: users/templates/users/user_password_authentication.html:18 #: users/templates/users/user_password_authentication.html:18
#: users/templates/users/user_password_update.html:44 #: users/templates/users/user_password_update.html:44
...@@ -1090,8 +1090,8 @@ msgstr "资产组" ...@@ -1090,8 +1090,8 @@ msgstr "资产组"
msgid "Default asset group" msgid "Default asset group"
msgstr "默认资产组" msgstr "默认资产组"
#: assets/models/label.py:15 audits/models.py:17 audits/models.py:37 #: assets/models/label.py:15 audits/models.py:18 audits/models.py:38
#: audits/models.py:50 audits/templates/audits/ftp_log_list.html:36 #: audits/models.py:51 audits/templates/audits/ftp_log_list.html:36
#: audits/templates/audits/ftp_log_list.html:73 #: audits/templates/audits/ftp_log_list.html:73
#: audits/templates/audits/operate_log_list.html:39 #: audits/templates/audits/operate_log_list.html:39
#: audits/templates/audits/operate_log_list.html:72 #: audits/templates/audits/operate_log_list.html:72
...@@ -1110,9 +1110,10 @@ msgstr "默认资产组" ...@@ -1110,9 +1110,10 @@ msgstr "默认资产组"
#: terminal/models.py:156 terminal/templates/terminal/command_list.html:29 #: terminal/models.py:156 terminal/templates/terminal/command_list.html:29
#: terminal/templates/terminal/command_list.html:65 #: terminal/templates/terminal/command_list.html:65
#: terminal/templates/terminal/session_list.html:27 #: terminal/templates/terminal/session_list.html:27
#: terminal/templates/terminal/session_list.html:71 users/forms.py:319 #: terminal/templates/terminal/session_list.html:71 users/forms.py:339
#: users/models/user.py:127 users/models/user.py:143 users/models/user.py:500 #: users/models/user.py:127 users/models/user.py:143 users/models/user.py:500
#: users/serializers/v1.py:132 users/templates/users/user_group_detail.html:78 #: users/serializers/group.py:21
#: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:36 users/views/user.py:250 #: users/templates/users/user_group_list.html:36 users/views/user.py:250
#: xpack/plugins/orgs/forms.py:28 #: xpack/plugins/orgs/forms.py:28
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113
...@@ -1120,7 +1121,7 @@ msgstr "默认资产组" ...@@ -1120,7 +1121,7 @@ msgstr "默认资产组"
msgid "User" msgid "User"
msgstr "用户" msgstr "用户"
#: assets/models/label.py:19 assets/models/node.py:412 #: assets/models/label.py:19 assets/models/node.py:453
#: assets/templates/assets/label_list.html:15 settings/models.py:30 #: assets/templates/assets/label_list.html:15 settings/models.py:30
msgid "Value" msgid "Value"
msgstr "值" msgstr "值"
...@@ -1129,23 +1130,23 @@ msgstr "值" ...@@ -1129,23 +1130,23 @@ msgstr "值"
msgid "Category" msgid "Category"
msgstr "分类" msgstr "分类"
#: assets/models/node.py:163 #: assets/models/node.py:164
msgid "New node" msgid "New node"
msgstr "新节点" msgstr "新节点"
#: assets/models/node.py:324 #: assets/models/node.py:325
msgid "ungrouped" msgid "ungrouped"
msgstr "未分组" msgstr "未分组"
#: assets/models/node.py:326 #: assets/models/node.py:327
msgid "empty" msgid "empty"
msgstr "空" msgstr "空"
#: assets/models/node.py:328 #: assets/models/node.py:329
msgid "favorite" msgid "favorite"
msgstr "收藏夹" msgstr "收藏夹"
#: assets/models/node.py:411 #: assets/models/node.py:452
msgid "Key" msgid "Key"
msgstr "键" msgstr "键"
...@@ -1200,7 +1201,7 @@ msgid "Login mode" ...@@ -1200,7 +1201,7 @@ msgid "Login mode"
msgstr "登录模式" msgstr "登录模式"
#: assets/models/user.py:166 assets/templates/assets/user_asset_list.html:79 #: assets/models/user.py:166 assets/templates/assets/user_asset_list.html:79
#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:52 #: audits/models.py:21 audits/templates/audits/ftp_log_list.html:52
#: audits/templates/audits/ftp_log_list.html:75 #: audits/templates/audits/ftp_log_list.html:75
#: perms/forms/asset_permission.py:90 perms/forms/remote_app_permission.py:43 #: perms/forms/asset_permission.py:90 perms/forms/remote_app_permission.py:43
#: perms/models/asset_permission.py:82 perms/models/remote_app_permission.py:16 #: perms/models/asset_permission.py:82 perms/models/remote_app_permission.py:16
...@@ -1265,7 +1266,7 @@ msgstr "组织名称" ...@@ -1265,7 +1266,7 @@ msgstr "组织名称"
msgid "Backend" msgid "Backend"
msgstr "后端" msgstr "后端"
#: assets/serializers/asset_user.py:67 users/forms.py:262 #: assets/serializers/asset_user.py:67 users/forms.py:282
#: users/models/user.py:403 users/templates/users/first_login.html:42 #: users/models/user.py:403 users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:49 #: users/templates/users/user_password_update.html:49
#: users/templates/users/user_profile.html:69 #: users/templates/users/user_profile.html:69
...@@ -1436,9 +1437,10 @@ msgid "Asset list" ...@@ -1436,9 +1437,10 @@ msgid "Asset list"
msgstr "资产列表" msgstr "资产列表"
#: assets/templates/assets/_asset_list_modal.html:33 #: assets/templates/assets/_asset_list_modal.html:33
#: assets/templates/assets/_node_tree.html:40 #: assets/templates/assets/_node_tree.html:39
#: ops/templates/ops/command_execution_create.html:70 #: ops/templates/ops/command_execution_create.html:70
#: ops/templates/ops/command_execution_create.html:127 #: ops/templates/ops/command_execution_create.html:127
#: settings/templates/settings/_ldap_list_users_modal.html:41
#: users/templates/users/_granted_assets.html:7 #: users/templates/users/_granted_assets.html:7
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66
msgid "Loading" msgid "Loading"
...@@ -1481,7 +1483,7 @@ msgstr "获取认证信息错误" ...@@ -1481,7 +1483,7 @@ msgstr "获取认证信息错误"
#: assets/templates/assets/_user_asset_detail_modal.html:23 #: assets/templates/assets/_user_asset_detail_modal.html:23
#: authentication/templates/authentication/_access_key_modal.html:142 #: authentication/templates/authentication/_access_key_modal.html:142
#: authentication/templates/authentication/_mfa_confirm_modal.html:53 #: authentication/templates/authentication/_mfa_confirm_modal.html:53
#: settings/templates/settings/_ldap_list_users_modal.html:92 #: settings/templates/settings/_ldap_list_users_modal.html:171
#: templates/_modal.html:22 #: templates/_modal.html:22
msgid "Close" msgid "Close"
msgstr "关闭" msgstr "关闭"
...@@ -1531,31 +1533,31 @@ msgstr "SSH端口" ...@@ -1531,31 +1533,31 @@ msgstr "SSH端口"
msgid "If use nat, set the ssh real port" msgid "If use nat, set the ssh real port"
msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口" msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口"
#: assets/templates/assets/_node_tree.html:50 #: assets/templates/assets/_node_tree.html:49
msgid "Add node" msgid "Add node"
msgstr "新建节点" msgstr "新建节点"
#: assets/templates/assets/_node_tree.html:51 #: assets/templates/assets/_node_tree.html:50
msgid "Rename node" msgid "Rename node"
msgstr "重命名节点" msgstr "重命名节点"
#: assets/templates/assets/_node_tree.html:52 #: assets/templates/assets/_node_tree.html:51
msgid "Delete node" msgid "Delete node"
msgstr "删除节点" msgstr "删除节点"
#: assets/templates/assets/_node_tree.html:166 #: assets/templates/assets/_node_tree.html:165
msgid "Create node failed" msgid "Create node failed"
msgstr "创建节点失败" msgstr "创建节点失败"
#: assets/templates/assets/_node_tree.html:178 #: assets/templates/assets/_node_tree.html:177
msgid "Have child node, cancel" msgid "Have child node, cancel"
msgstr "存在子节点,不能删除" msgstr "存在子节点,不能删除"
#: assets/templates/assets/_node_tree.html:180 #: assets/templates/assets/_node_tree.html:179
msgid "Have assets, cancel" msgid "Have assets, cancel"
msgstr "存在资产,不能删除" msgstr "存在资产,不能删除"
#: assets/templates/assets/_node_tree.html:255 #: assets/templates/assets/_node_tree.html:254
msgid "Rename success" msgid "Rename success"
msgstr "重命名成功" msgstr "重命名成功"
...@@ -1697,7 +1699,7 @@ msgstr "导出" ...@@ -1697,7 +1699,7 @@ msgstr "导出"
#: assets/templates/assets/admin_user_list.html:21 #: assets/templates/assets/admin_user_list.html:21
#: assets/templates/assets/asset_list.html:73 #: assets/templates/assets/asset_list.html:73
#: assets/templates/assets/system_user_list.html:24 #: assets/templates/assets/system_user_list.html:24
#: settings/templates/settings/_ldap_list_users_modal.html:93 #: settings/templates/settings/_ldap_list_users_modal.html:172
#: users/templates/users/user_group_list.html:15 #: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:15 #: users/templates/users/user_list.html:15
#: xpack/plugins/license/templates/license/license_detail.html:110 #: xpack/plugins/license/templates/license/license_detail.html:110
...@@ -1882,16 +1884,16 @@ msgstr "删除选择资产" ...@@ -1882,16 +1884,16 @@ msgstr "删除选择资产"
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
#: assets/templates/assets/asset_list.html:434 #: assets/templates/assets/asset_list.html:432
msgid "Asset Deleted." msgid "Asset Deleted."
msgstr "已被删除" msgstr "已被删除"
#: assets/templates/assets/asset_list.html:435 #: assets/templates/assets/asset_list.html:433
#: assets/templates/assets/asset_list.html:439 #: assets/templates/assets/asset_list.html:441
msgid "Asset Delete" msgid "Asset Delete"
msgstr "删除" msgstr "删除"
#: assets/templates/assets/asset_list.html:438 #: assets/templates/assets/asset_list.html:440
msgid "Asset Deleting failed." msgid "Asset Deleting failed."
msgstr "删除失败" msgstr "删除失败"
...@@ -1983,10 +1985,6 @@ msgstr "测试连接" ...@@ -1983,10 +1985,6 @@ msgstr "测试连接"
msgid "Can be connected" msgid "Can be connected"
msgstr "可连接" msgstr "可连接"
#: assets/templates/assets/domain_gateway_list.html:142
msgid "The connection fails"
msgstr "连接失败"
#: assets/templates/assets/domain_list.html:9 #: assets/templates/assets/domain_list.html:9
msgid "" msgid ""
"The domain function is added to address the fact that some environments " "The domain function is added to address the fact that some environments "
...@@ -2180,7 +2178,7 @@ msgstr "资产管理" ...@@ -2180,7 +2178,7 @@ msgstr "资产管理"
msgid "System user asset" msgid "System user asset"
msgstr "系统用户资产" msgstr "系统用户资产"
#: audits/models.py:18 audits/models.py:41 audits/models.py:52 #: audits/models.py:19 audits/models.py:42 audits/models.py:53
#: audits/templates/audits/ftp_log_list.html:76 #: audits/templates/audits/ftp_log_list.html:76
#: audits/templates/audits/operate_log_list.html:76 #: audits/templates/audits/operate_log_list.html:76
#: audits/templates/audits/password_change_log_list.html:58 #: audits/templates/audits/password_change_log_list.html:58
...@@ -2190,16 +2188,16 @@ msgstr "系统用户资产" ...@@ -2190,16 +2188,16 @@ msgstr "系统用户资产"
msgid "Remote addr" msgid "Remote addr"
msgstr "远端地址" msgstr "远端地址"
#: audits/models.py:21 audits/templates/audits/ftp_log_list.html:77 #: audits/models.py:22 audits/templates/audits/ftp_log_list.html:77
msgid "Operate" msgid "Operate"
msgstr "操作" msgstr "操作"
#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:59 #: audits/models.py:23 audits/templates/audits/ftp_log_list.html:59
#: audits/templates/audits/ftp_log_list.html:78 #: audits/templates/audits/ftp_log_list.html:78
msgid "Filename" msgid "Filename"
msgstr "文件名" msgstr "文件名"
#: audits/models.py:23 audits/models.py:76 #: audits/models.py:24 audits/models.py:77
#: audits/templates/audits/ftp_log_list.html:79 #: audits/templates/audits/ftp_log_list.html:79
#: ops/templates/ops/command_execution_list.html:68 #: ops/templates/ops/command_execution_list.html:68
#: ops/templates/ops/task_list.html:15 #: ops/templates/ops/task_list.html:15
...@@ -2209,67 +2207,67 @@ msgstr "文件名" ...@@ -2209,67 +2207,67 @@ msgstr "文件名"
msgid "Success" msgid "Success"
msgstr "成功" msgstr "成功"
#: audits/models.py:32 #: audits/models.py:33
#: authentication/templates/authentication/_access_key_modal.html:22 #: authentication/templates/authentication/_access_key_modal.html:22
#: xpack/plugins/vault/templates/vault/vault.html:46 #: xpack/plugins/vault/templates/vault/vault.html:46
msgid "Create" msgid "Create"
msgstr "创建" msgstr "创建"
#: audits/models.py:39 audits/templates/audits/operate_log_list.html:55 #: audits/models.py:40 audits/templates/audits/operate_log_list.html:55
#: audits/templates/audits/operate_log_list.html:74 #: audits/templates/audits/operate_log_list.html:74
msgid "Resource Type" msgid "Resource Type"
msgstr "资源类型" msgstr "资源类型"
#: audits/models.py:40 audits/templates/audits/operate_log_list.html:75 #: audits/models.py:41 audits/templates/audits/operate_log_list.html:75
msgid "Resource" msgid "Resource"
msgstr "资源" msgstr "资源"
#: audits/models.py:51 audits/templates/audits/password_change_log_list.html:57 #: audits/models.py:52 audits/templates/audits/password_change_log_list.html:57
msgid "Change by" msgid "Change by"
msgstr "修改者" msgstr "修改者"
#: audits/models.py:70 users/templates/users/user_detail.html:98 #: audits/models.py:71 users/templates/users/user_detail.html:98
msgid "Disabled" msgid "Disabled"
msgstr "禁用" msgstr "禁用"
#: audits/models.py:71 settings/models.py:33 #: audits/models.py:72 settings/models.py:33
#: users/templates/users/user_detail.html:96 #: users/templates/users/user_detail.html:96
msgid "Enabled" msgid "Enabled"
msgstr "启用" msgstr "启用"
#: audits/models.py:72 #: audits/models.py:73
msgid "-" msgid "-"
msgstr "" msgstr ""
#: audits/models.py:77 xpack/plugins/cloud/models.py:264 #: audits/models.py:78 xpack/plugins/cloud/models.py:264
#: xpack/plugins/cloud/models.py:287 #: xpack/plugins/cloud/models.py:287
msgid "Failed" msgid "Failed"
msgstr "失败" msgstr "失败"
#: audits/models.py:81 #: audits/models.py:82
msgid "Login type" msgid "Login type"
msgstr "登录方式" msgstr "登录方式"
#: audits/models.py:82 #: audits/models.py:83
msgid "Login ip" msgid "Login ip"
msgstr "登录IP" msgstr "登录IP"
#: audits/models.py:83 #: audits/models.py:84
msgid "Login city" msgid "Login city"
msgstr "登录城市" msgstr "登录城市"
#: audits/models.py:84 #: audits/models.py:85
msgid "User agent" msgid "User agent"
msgstr "Agent" msgstr "Agent"
#: audits/models.py:85 audits/templates/audits/login_log_list.html:62 #: audits/models.py:86 audits/templates/audits/login_log_list.html:62
#: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: users/forms.py:174 users/models/user.py:395 #: users/forms.py:194 users/models/user.py:395
#: users/templates/users/first_login.html:45 #: users/templates/users/first_login.html:45
msgid "MFA" msgid "MFA"
msgstr "MFA" msgstr "MFA"
#: audits/models.py:86 audits/templates/audits/login_log_list.html:63 #: audits/models.py:87 audits/templates/audits/login_log_list.html:63
#: xpack/plugins/change_auth_plan/models.py:416 #: xpack/plugins/change_auth_plan/models.py:416
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
#: xpack/plugins/cloud/models.py:278 #: xpack/plugins/cloud/models.py:278
...@@ -2277,14 +2275,14 @@ msgstr "MFA" ...@@ -2277,14 +2275,14 @@ msgstr "MFA"
msgid "Reason" msgid "Reason"
msgstr "原因" msgstr "原因"
#: audits/models.py:87 audits/templates/audits/login_log_list.html:64 #: audits/models.py:88 audits/templates/audits/login_log_list.html:64
#: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310 #: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65
msgid "Status" msgid "Status"
msgstr "状态" msgstr "状态"
#: audits/models.py:88 #: audits/models.py:89
msgid "Date login" msgid "Date login"
msgstr "登录日期" msgstr "登录日期"
...@@ -2487,7 +2485,7 @@ msgid "" ...@@ -2487,7 +2485,7 @@ msgid ""
"after {} minutes)" "after {} minutes)"
msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)" msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)"
#: authentication/forms.py:66 users/forms.py:21 #: authentication/forms.py:66 users/forms.py:22
msgid "MFA code" msgid "MFA code"
msgstr "MFA 验证码" msgstr "MFA 验证码"
...@@ -2826,23 +2824,23 @@ msgstr "Become" ...@@ -2826,23 +2824,23 @@ msgstr "Become"
msgid "Create by" msgid "Create by"
msgstr "创建者" msgstr "创建者"
#: ops/models/adhoc.py:251 #: ops/models/adhoc.py:252
msgid "{} Start task: {}" msgid "{} Start task: {}"
msgstr "{} 任务开始: {}" msgstr "{} 任务开始: {}"
#: ops/models/adhoc.py:263 #: ops/models/adhoc.py:264
msgid "{} Task finish" msgid "{} Task finish"
msgstr "{} 任务结束" msgstr "{} 任务结束"
#: ops/models/adhoc.py:355 #: ops/models/adhoc.py:356
msgid "Start time" msgid "Start time"
msgstr "开始时间" msgstr "开始时间"
#: ops/models/adhoc.py:356 #: ops/models/adhoc.py:357
msgid "End time" msgid "End time"
msgstr "完成时间" msgstr "完成时间"
#: ops/models/adhoc.py:357 ops/templates/ops/adhoc_history.html:57 #: ops/models/adhoc.py:358 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:17 #: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:17
#: xpack/plugins/change_auth_plan/models.py:252 #: xpack/plugins/change_auth_plan/models.py:252
#: xpack/plugins/change_auth_plan/models.py:422 #: xpack/plugins/change_auth_plan/models.py:422
...@@ -2852,23 +2850,23 @@ msgstr "完成时间" ...@@ -2852,23 +2850,23 @@ msgstr "完成时间"
msgid "Time" msgid "Time"
msgstr "时间" msgstr "时间"
#: ops/models/adhoc.py:358 ops/templates/ops/adhoc_detail.html:106 #: ops/models/adhoc.py:359 ops/templates/ops/adhoc_detail.html:106
#: ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/adhoc_history.html:55
#: ops/templates/ops/adhoc_history_detail.html:69 #: ops/templates/ops/adhoc_history_detail.html:69
#: ops/templates/ops/task_detail.html:84 ops/templates/ops/task_history.html:61 #: ops/templates/ops/task_detail.html:84 ops/templates/ops/task_history.html:61
msgid "Is finished" msgid "Is finished"
msgstr "是否完成" msgstr "是否完成"
#: ops/models/adhoc.py:359 ops/templates/ops/adhoc_history.html:56 #: ops/models/adhoc.py:360 ops/templates/ops/adhoc_history.html:56
#: ops/templates/ops/task_history.html:62 #: ops/templates/ops/task_history.html:62
msgid "Is success" msgid "Is success"
msgstr "是否成功" msgstr "是否成功"
#: ops/models/adhoc.py:360 #: ops/models/adhoc.py:361
msgid "Adhoc raw result" msgid "Adhoc raw result"
msgstr "结果" msgstr "结果"
#: ops/models/adhoc.py:361 #: ops/models/adhoc.py:362
msgid "Adhoc result summary" msgid "Adhoc result summary"
msgstr "汇总" msgstr "汇总"
...@@ -3135,7 +3133,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" ...@@ -3135,7 +3133,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
#: perms/templates/perms/asset_permission_list.html:71 #: perms/templates/perms/asset_permission_list.html:71
#: perms/templates/perms/asset_permission_list.html:118 #: perms/templates/perms/asset_permission_list.html:118
#: perms/templates/perms/remote_app_permission_list.html:16 #: perms/templates/perms/remote_app_permission_list.html:16
#: templates/_nav.html:21 users/forms.py:293 users/models/group.py:26 #: templates/_nav.html:21 users/forms.py:313 users/models/group.py:26
#: users/models/user.py:379 users/templates/users/_select_user_modal.html:16 #: users/models/user.py:379 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:218 #: users/templates/users/user_detail.html:218
#: users/templates/users/user_list.html:38 #: users/templates/users/user_list.html:38
...@@ -3395,33 +3393,41 @@ msgstr "远程应用授权用户列表" ...@@ -3395,33 +3393,41 @@ msgstr "远程应用授权用户列表"
msgid "RemoteApp permission RemoteApp list" msgid "RemoteApp permission RemoteApp list"
msgstr "远程应用授权远程应用列表" msgstr "远程应用授权远程应用列表"
#: settings/api.py:28 #: settings/api.py:37
msgid "Test mail sent to {}, please check" msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查" msgstr "邮件已经发送{}, 请检查"
#: settings/api.py:67 #: settings/api.py:76
msgid "Test ldap success" msgid "Test ldap success"
msgstr "连接LDAP成功" msgstr "连接LDAP成功"
#: settings/api.py:104 #: settings/api.py:107
msgid "LDAP attr map not valid"
msgstr "LDAP 属性映射无效"
#: settings/api.py:116
msgid "Match {} s users" msgid "Match {} s users"
msgstr "匹配 {} 个用户" msgstr "匹配 {} 个用户"
#: settings/api.py:163 #: settings/api.py:224
msgid "succeed: {} failed: {} total: {}" msgid "Get ldap users is None"
msgstr "成功:{} 失败:{} 总数:{}" msgstr "获取 LDAP 用户为 None"
#: settings/api.py:185 settings/api.py:221 #: settings/api.py:231
msgid "Imported {} users successfully"
msgstr "导入 {} 个用户成功"
#: settings/api.py:262 settings/api.py:298
msgid "" msgid ""
"Error: Account invalid (Please make sure the information such as Access key " "Error: Account invalid (Please make sure the information such as Access key "
"or Secret key is correct)" "or Secret key is correct)"
msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)" msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)"
#: settings/api.py:191 settings/api.py:227 #: settings/api.py:268 settings/api.py:304
msgid "Create succeed" msgid "Create succeed"
msgstr "创建成功" msgstr "创建成功"
#: settings/api.py:209 settings/api.py:247 #: settings/api.py:286 settings/api.py:324
#: settings/templates/settings/terminal_setting.html:154 #: settings/templates/settings/terminal_setting.html:154
msgid "Delete succeed" msgid "Delete succeed"
msgstr "删除成功" msgstr "删除成功"
...@@ -3741,23 +3747,32 @@ msgstr "LDAP 用户列表" ...@@ -3741,23 +3747,32 @@ msgstr "LDAP 用户列表"
msgid "Please submit the LDAP configuration before import" msgid "Please submit the LDAP configuration before import"
msgstr "请先提交LDAP配置再进行导入" msgstr "请先提交LDAP配置再进行导入"
#: settings/templates/settings/_ldap_list_users_modal.html:32 #: settings/templates/settings/_ldap_list_users_modal.html:26
msgid "Refresh cache"
msgstr "刷新缓存"
#: settings/templates/settings/_ldap_list_users_modal.html:33
#: users/models/user.py:375 users/templates/users/user_detail.html:71 #: users/models/user.py:375 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59 #: users/templates/users/user_profile.html:59
msgid "Email" msgid "Email"
msgstr "邮件" msgstr "邮件"
#: settings/templates/settings/_ldap_list_users_modal.html:33 #: settings/templates/settings/_ldap_list_users_modal.html:34
msgid "Existing" msgid "Existing"
msgstr "已存在" msgstr "已存在"
#: settings/templates/settings/_ldap_list_users_modal.html:144
msgid ""
"User is not currently selected, please check the user you want to import"
msgstr "当前无勾选用户,请勾选你想要导入的用户"
#: settings/templates/settings/basic_setting.html:15 #: settings/templates/settings/basic_setting.html:15
#: settings/templates/settings/email_content_setting.html:15 #: settings/templates/settings/email_content_setting.html:15
#: settings/templates/settings/email_setting.html:15 #: settings/templates/settings/email_setting.html:15
#: settings/templates/settings/ldap_setting.html:15 #: settings/templates/settings/ldap_setting.html:15
#: settings/templates/settings/security_setting.html:15 #: settings/templates/settings/security_setting.html:15
#: settings/templates/settings/terminal_setting.html:16 #: settings/templates/settings/terminal_setting.html:16
#: settings/templates/settings/terminal_setting.html:49 settings/views.py:20 #: settings/templates/settings/terminal_setting.html:49 settings/views.py:21
msgid "Basic setting" msgid "Basic setting"
msgstr "基本设置" msgstr "基本设置"
...@@ -3766,7 +3781,7 @@ msgstr "基本设置" ...@@ -3766,7 +3781,7 @@ msgstr "基本设置"
#: settings/templates/settings/email_setting.html:18 #: settings/templates/settings/email_setting.html:18
#: settings/templates/settings/ldap_setting.html:18 #: settings/templates/settings/ldap_setting.html:18
#: settings/templates/settings/security_setting.html:18 #: settings/templates/settings/security_setting.html:18
#: settings/templates/settings/terminal_setting.html:20 settings/views.py:47 #: settings/templates/settings/terminal_setting.html:20 settings/views.py:48
msgid "Email setting" msgid "Email setting"
msgstr "邮件设置" msgstr "邮件设置"
...@@ -3775,7 +3790,7 @@ msgstr "邮件设置" ...@@ -3775,7 +3790,7 @@ msgstr "邮件设置"
#: settings/templates/settings/email_setting.html:21 #: settings/templates/settings/email_setting.html:21
#: settings/templates/settings/ldap_setting.html:21 #: settings/templates/settings/ldap_setting.html:21
#: settings/templates/settings/security_setting.html:21 #: settings/templates/settings/security_setting.html:21
#: settings/templates/settings/terminal_setting.html:23 settings/views.py:186 #: settings/templates/settings/terminal_setting.html:23 settings/views.py:188
msgid "Email content setting" msgid "Email content setting"
msgstr "邮件内容设置" msgstr "邮件内容设置"
...@@ -3784,7 +3799,7 @@ msgstr "邮件内容设置" ...@@ -3784,7 +3799,7 @@ msgstr "邮件内容设置"
#: settings/templates/settings/email_setting.html:24 #: settings/templates/settings/email_setting.html:24
#: settings/templates/settings/ldap_setting.html:24 #: settings/templates/settings/ldap_setting.html:24
#: settings/templates/settings/security_setting.html:24 #: settings/templates/settings/security_setting.html:24
#: settings/templates/settings/terminal_setting.html:27 settings/views.py:74 #: settings/templates/settings/terminal_setting.html:27 settings/views.py:75
msgid "LDAP setting" msgid "LDAP setting"
msgstr "LDAP设置" msgstr "LDAP设置"
...@@ -3793,7 +3808,7 @@ msgstr "LDAP设置" ...@@ -3793,7 +3808,7 @@ msgstr "LDAP设置"
#: settings/templates/settings/email_setting.html:27 #: settings/templates/settings/email_setting.html:27
#: settings/templates/settings/ldap_setting.html:27 #: settings/templates/settings/ldap_setting.html:27
#: settings/templates/settings/security_setting.html:27 #: settings/templates/settings/security_setting.html:27
#: settings/templates/settings/terminal_setting.html:31 settings/views.py:104 #: settings/templates/settings/terminal_setting.html:31 settings/views.py:106
msgid "Terminal setting" msgid "Terminal setting"
msgstr "终端设置" msgstr "终端设置"
...@@ -3803,7 +3818,7 @@ msgstr "终端设置" ...@@ -3803,7 +3818,7 @@ msgstr "终端设置"
#: settings/templates/settings/ldap_setting.html:30 #: settings/templates/settings/ldap_setting.html:30
#: settings/templates/settings/security_setting.html:30 #: settings/templates/settings/security_setting.html:30
#: settings/templates/settings/security_setting.html:45 #: settings/templates/settings/security_setting.html:45
#: settings/templates/settings/terminal_setting.html:34 settings/views.py:159 #: settings/templates/settings/terminal_setting.html:34 settings/views.py:161
msgid "Security setting" msgid "Security setting"
msgstr "安全设置" msgstr "安全设置"
...@@ -3823,15 +3838,10 @@ msgstr "文档类型" ...@@ -3823,15 +3838,10 @@ msgstr "文档类型"
msgid "Create User setting" msgid "Create User setting"
msgstr "创建用户设置" msgstr "创建用户设置"
#: settings/templates/settings/ldap_setting.html:68 #: settings/templates/settings/ldap_setting.html:66
msgid "Bulk import" msgid "Bulk import"
msgstr "一键导入" msgstr "一键导入"
#: settings/templates/settings/ldap_setting.html:116
msgid ""
"User is not currently selected, please check the user you want to import"
msgstr "当前无勾选用户,请勾选你想要导入的用户"
#: settings/templates/settings/replay_storage_create.html:66 #: settings/templates/settings/replay_storage_create.html:66
msgid "Bucket" msgid "Bucket"
msgstr "桶名称" msgstr "桶名称"
...@@ -3936,30 +3946,26 @@ msgstr "删除失败" ...@@ -3936,30 +3946,26 @@ msgstr "删除失败"
msgid "Are you sure about deleting it?" msgid "Are you sure about deleting it?"
msgstr "您确定删除吗?" msgstr "您确定删除吗?"
#: settings/utils.py:98 #: settings/utils/ldap.py:128
msgid "Search no entry matched in ou {}" msgid "Search no entry matched in ou {}"
msgstr "在ou:{}中没有匹配条目" msgstr "在ou:{}中没有匹配条目"
#: settings/utils.py:172 #: settings/views.py:20 settings/views.py:47 settings/views.py:74
msgid "The user source is not LDAP" #: settings/views.py:105 settings/views.py:133 settings/views.py:146
msgstr "用户来源不是LDAP" #: settings/views.py:160 settings/views.py:187 templates/_nav.html:170
#: settings/views.py:19 settings/views.py:46 settings/views.py:73
#: settings/views.py:103 settings/views.py:131 settings/views.py:144
#: settings/views.py:158 settings/views.py:185 templates/_nav.html:170
msgid "Settings" msgid "Settings"
msgstr "系统设置" msgstr "系统设置"
#: settings/views.py:30 settings/views.py:57 settings/views.py:84 #: settings/views.py:31 settings/views.py:58 settings/views.py:85
#: settings/views.py:116 settings/views.py:169 settings/views.py:196 #: settings/views.py:118 settings/views.py:171 settings/views.py:198
msgid "Update setting successfully" msgid "Update setting successfully"
msgstr "更新设置成功" msgstr "更新设置成功"
#: settings/views.py:132 #: settings/views.py:134
msgid "Create replay storage" msgid "Create replay storage"
msgstr "创建录像存储" msgstr "创建录像存储"
#: settings/views.py:145 #: settings/views.py:147
msgid "Create command storage" msgid "Create command storage"
msgstr "创建命令存储" msgstr "创建命令存储"
...@@ -3976,8 +3982,8 @@ msgid "Commercial support" ...@@ -3976,8 +3982,8 @@ msgid "Commercial support"
msgstr "商业支持" msgstr "商业支持"
#: templates/_header_bar.html:70 templates/_nav.html:30 #: templates/_header_bar.html:70 templates/_nav.html:30
#: templates/_nav_user.html:32 users/forms.py:153 #: templates/_nav_user.html:32 users/forms.py:173
#: users/templates/users/_user.html:43 #: users/templates/users/_user.html:44
#: users/templates/users/first_login.html:39 #: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:40 #: users/templates/users/user_password_update.html:40
#: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile.html:17
...@@ -4541,7 +4547,7 @@ msgstr "你可以使用ssh客户端工具连接终端" ...@@ -4541,7 +4547,7 @@ msgstr "你可以使用ssh客户端工具连接终端"
msgid "Could not reset self otp, use profile reset instead" msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置" msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
#: users/forms.py:32 users/models/user.py:383 #: users/forms.py:47 users/models/user.py:383
#: users/templates/users/_select_user_modal.html:15 #: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87 #: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:37 #: users/templates/users/user_list.html:37
...@@ -4549,44 +4555,51 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置" ...@@ -4549,44 +4555,51 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
msgid "Role" msgid "Role"
msgstr "角色" msgstr "角色"
#: users/forms.py:35 users/forms.py:232 #: users/forms.py:51 users/models/user.py:418
#: users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:39
#: users/templates/users/user_profile.html:102
msgid "Source"
msgstr "用户来源"
#: users/forms.py:54 users/forms.py:252
#: users/templates/users/user_update.html:30 #: users/templates/users/user_update.html:30
msgid "ssh public key" msgid "ssh public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: users/forms.py:36 users/forms.py:233 #: users/forms.py:55 users/forms.py:253
msgid "ssh-rsa AAAA..." msgid "ssh-rsa AAAA..."
msgstr "" msgstr ""
#: users/forms.py:37 #: users/forms.py:56
msgid "Paste user id_rsa.pub here." msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里" msgstr "复制用户公钥到这里"
#: users/forms.py:51 users/templates/users/user_detail.html:226 #: users/forms.py:71 users/templates/users/user_detail.html:226
msgid "Join user groups" msgid "Join user groups"
msgstr "添加到用户组" msgstr "添加到用户组"
#: users/forms.py:86 users/forms.py:247 #: users/forms.py:106 users/forms.py:267
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms.py:90 users/forms.py:251 users/serializers/v1.py:116 #: users/forms.py:110 users/forms.py:271 users/serializers/user.py:110
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法" msgstr "ssh密钥不合法"
#: users/forms.py:103 users/views/login.py:114 users/views/user.py:287 #: users/forms.py:123 users/views/login.py:114 users/views/user.py:287
msgid "* Your password does not meet the requirements" msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求" msgstr "* 您的密码不符合要求"
#: users/forms.py:124 #: users/forms.py:144
msgid "Reset link will be generated and sent to the user" msgid "Reset link will be generated and sent to the user"
msgstr "生成重置密码链接,通过邮件发送给用户" msgstr "生成重置密码链接,通过邮件发送给用户"
#: users/forms.py:125 #: users/forms.py:145
msgid "Set password" msgid "Set password"
msgstr "设置密码" msgstr "设置密码"
#: users/forms.py:132 xpack/plugins/change_auth_plan/models.py:88 #: users/forms.py:152 xpack/plugins/change_auth_plan/models.py:88
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57
...@@ -4594,7 +4607,7 @@ msgstr "设置密码" ...@@ -4594,7 +4607,7 @@ msgstr "设置密码"
msgid "Password strategy" msgid "Password strategy"
msgstr "密码策略" msgstr "密码策略"
#: users/forms.py:159 #: users/forms.py:179
msgid "" msgid ""
"When enabled, you will enter the MFA binding process the next time you log " "When enabled, you will enter the MFA binding process the next time you log "
"in. you can also directly bind in \"personal information -> quick " "in. you can also directly bind in \"personal information -> quick "
...@@ -4603,11 +4616,11 @@ msgstr "" ...@@ -4603,11 +4616,11 @@ msgstr ""
"启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修改->更" "启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修改->更"
"改MFA设置)中直接绑定!" "改MFA设置)中直接绑定!"
#: users/forms.py:169 #: users/forms.py:189
msgid "* Enable MFA authentication to make the account more secure." msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全。" msgstr "* 启用MFA认证,使账号更加安全。"
#: users/forms.py:179 #: users/forms.py:199
msgid "" msgid ""
"In order to protect you and your company, please keep your account, password " "In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex " "and key sensitive information properly. (for example: setting complex "
...@@ -4616,41 +4629,41 @@ msgstr "" ...@@ -4616,41 +4629,41 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)" "设置复杂密码,启用MFA认证)"
#: users/forms.py:186 users/templates/users/first_login.html:48 #: users/forms.py:206 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:110 #: users/templates/users/first_login.html:110
#: users/templates/users/first_login.html:139 #: users/templates/users/first_login.html:139
msgid "Finish" msgid "Finish"
msgstr "完成" msgstr "完成"
#: users/forms.py:192 #: users/forms.py:212
msgid "Old password" msgid "Old password"
msgstr "原来密码" msgstr "原来密码"
#: users/forms.py:197 #: users/forms.py:217
msgid "New password" msgid "New password"
msgstr "新密码" msgstr "新密码"
#: users/forms.py:202 #: users/forms.py:222
msgid "Confirm password" msgid "Confirm password"
msgstr "确认密码" msgstr "确认密码"
#: users/forms.py:212 #: users/forms.py:232
msgid "Old password error" msgid "Old password error"
msgstr "原来密码错误" msgstr "原来密码错误"
#: users/forms.py:220 #: users/forms.py:240
msgid "Password does not match" msgid "Password does not match"
msgstr "密码不一致" msgstr "密码不一致"
#: users/forms.py:230 #: users/forms.py:250
msgid "Automatically configure and download the SSH key" msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥" msgstr "自动配置并下载SSH密钥"
#: users/forms.py:234 #: users/forms.py:254
msgid "Paste your id_rsa.pub here." msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里" msgstr "复制你的公钥到这里"
#: users/forms.py:268 users/forms.py:273 users/forms.py:323 #: users/forms.py:288 users/forms.py:293 users/forms.py:343
#: xpack/plugins/orgs/forms.py:18 #: xpack/plugins/orgs/forms.py:18
msgid "Select users" msgid "Select users"
msgstr "选择用户" msgstr "选择用户"
...@@ -4693,12 +4706,6 @@ msgstr "头像" ...@@ -4693,12 +4706,6 @@ msgstr "头像"
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:418 users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:39
#: users/templates/users/user_profile.html:102
msgid "Source"
msgstr "用户来源"
#: users/models/user.py:422 #: users/models/user.py:422
msgid "Date password last updated" msgid "Date password last updated"
msgstr "最后更新密码日期" msgstr "最后更新密码日期"
...@@ -4707,46 +4714,46 @@ msgstr "最后更新密码日期" ...@@ -4707,46 +4714,46 @@ msgstr "最后更新密码日期"
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
#: users/serializers/v1.py:45 #: users/serializers/group.py:46
msgid "Auditors cannot be join in the user group"
msgstr "审计员不能被加入到用户组"
#: users/serializers/user.py:40
msgid "Groups name" msgid "Groups name"
msgstr "用户组名" msgstr "用户组名"
#: users/serializers/v1.py:46 #: users/serializers/user.py:41
msgid "Source name" msgid "Source name"
msgstr "用户来源名" msgstr "用户来源名"
#: users/serializers/v1.py:47 #: users/serializers/user.py:42
msgid "Is first login" msgid "Is first login"
msgstr "首次登录" msgstr "首次登录"
#: users/serializers/v1.py:48 #: users/serializers/user.py:43
msgid "Role name" msgid "Role name"
msgstr "角色名" msgstr "角色名"
#: users/serializers/v1.py:49 #: users/serializers/user.py:44
msgid "Is valid" msgid "Is valid"
msgstr "账户是否有效" msgstr "账户是否有效"
#: users/serializers/v1.py:50 #: users/serializers/user.py:45
msgid "Is expired" msgid "Is expired"
msgstr " 是否过期" msgstr " 是否过期"
#: users/serializers/v1.py:51 #: users/serializers/user.py:46
msgid "Avatar url" msgid "Avatar url"
msgstr "头像路径" msgstr "头像路径"
#: users/serializers/v1.py:72 #: users/serializers/user.py:66
msgid "Role limit to {}" msgid "Role limit to {}"
msgstr "角色只能为 {}" msgstr "角色只能为 {}"
#: users/serializers/v1.py:84 #: users/serializers/user.py:78
msgid "Password does not match security rules" msgid "Password does not match security rules"
msgstr "密码不满足安全规则" msgstr "密码不满足安全规则"
#: users/serializers/v1.py:157
msgid "Auditors cannot be join in the user group"
msgstr "审计员不能被加入到用户组"
#: users/serializers_v2/user.py:36 #: users/serializers_v2/user.py:36
msgid "name not unique" msgid "name not unique"
msgstr "名称重复" msgstr "名称重复"
...@@ -4779,7 +4786,7 @@ msgstr "选择用户" ...@@ -4779,7 +4786,7 @@ msgstr "选择用户"
msgid "Asset num" msgid "Asset num"
msgstr "资产数量" msgstr "资产数量"
#: users/templates/users/_user.html:26 #: users/templates/users/_user.html:27
msgid "Security and Role" msgid "Security and Role"
msgstr "角色安全" msgstr "角色安全"
...@@ -6206,6 +6213,14 @@ msgstr "密码匣子" ...@@ -6206,6 +6213,14 @@ msgstr "密码匣子"
msgid "vault create" msgid "vault create"
msgstr "创建" msgstr "创建"
#, fuzzy
#~| msgid "Password should not contain special characters"
#~ msgid "Password has special char"
#~ msgstr "不能包含特殊字符"
#~ msgid "The connection fails"
#~ msgstr "连接失败"
#~ msgid "Recipient" #~ msgid "Recipient"
#~ msgstr "收件人" #~ msgstr "收件人"
...@@ -6331,25 +6346,6 @@ msgstr "创建" ...@@ -6331,25 +6346,6 @@ msgstr "创建"
#~ msgid "Sync User" #~ msgid "Sync User"
#~ msgstr "同步用户" #~ msgstr "同步用户"
#~ msgid "Have user but attr mapping error"
#~ msgstr "有用户但attr映射错误"
#~ msgid ""
#~ "Import {} users successfully; import {} users failed, the database "
#~ "already exists with the same name"
#~ msgstr "导入 {} 个用户成功; 导入 {} 这些用户失败,数据库已经存在同名的用户"
#~ msgid ""
#~ "Import {} users successfully; import {} users failed, the database "
#~ "already exists with the same name; import {}users failed, "
#~ "Because’TypeError' object has no attribute 'keys'"
#~ msgstr ""
#~ "导入 {} 个用户成功; 导入 {} 这些用户失败,数据库已经存在同名的用户; 导入 "
#~ "{} 这些用户失败,因为对象没有属性'keys'"
#~ msgid "Import {} users successfully"
#~ msgstr "导入 {} 个用户成功"
#~ msgid "" #~ msgid ""
#~ "Import {} users successfully;import {} users failed, Because’TypeError' " #~ "Import {} users successfully;import {} users failed, Because’TypeError' "
#~ "object has no attribute 'keys'" #~ "object has no attribute 'keys'"
......
...@@ -11,13 +11,6 @@ from .utils.asset_permission import AssetPermissionUtilV2 ...@@ -11,13 +11,6 @@ from .utils.asset_permission import AssetPermissionUtilV2
logger = get_logger(__file__) logger = get_logger(__file__)
permission_m2m_senders = (
AssetPermission.nodes.through,
AssetPermission.assets.through,
AssetPermission.users.through,
AssetPermission.user_groups.through,
)
@receiver([post_save, post_delete], sender=AssetPermission) @receiver([post_save, post_delete], sender=AssetPermission)
@on_transaction_commit @on_transaction_commit
......
...@@ -13,17 +13,26 @@ from django.core.mail import send_mail ...@@ -13,17 +13,26 @@ from django.core.mail import send_mail
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .models import Setting from .models import Setting
from .utils import LDAPUtil from .utils import (
LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil,
LDAP_USE_CACHE_FLAGS
)
from .tasks import sync_ldap_user_task
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 MailTestSerializer, LDAPTestSerializer, LDAPUserSerializer from .serializers import (
MailTestSerializer, LDAPTestSerializer, LDAPUserSerializer,
PublicSettingSerializer,
)
from users.models import User
logger = get_logger(__file__) logger = get_logger(__file__)
class MailTestingAPI(APIView): class MailTestingAPI(APIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsSuperUser,)
serializer_class = MailTestSerializer serializer_class = MailTestSerializer
success_message = _("Test mail sent to {}, please check") success_message = _("Test mail sent to {}, please check")
...@@ -62,70 +71,83 @@ class MailTestingAPI(APIView): ...@@ -62,70 +71,83 @@ class MailTestingAPI(APIView):
class LDAPTestingAPI(APIView): class LDAPTestingAPI(APIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsSuperUser,)
serializer_class = LDAPTestSerializer serializer_class = LDAPTestSerializer
success_message = _("Test ldap success") success_message = _("Test ldap success")
@staticmethod @staticmethod
def get_ldap_util(serializer): def get_ldap_config(serializer):
host = serializer.validated_data["AUTH_LDAP_SERVER_URI"] server_uri = serializer.validated_data["AUTH_LDAP_SERVER_URI"]
bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"] bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"]
password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"] password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"]
use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False) use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False)
search_ougroup = serializer.validated_data["AUTH_LDAP_SEARCH_OU"] search_ougroup = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"] search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"] attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
try: config = {
attr_map = json.loads(attr_map) 'server_uri': server_uri,
except json.JSONDecodeError: 'bind_dn': bind_dn,
return Response({"error": "AUTH_LDAP_USER_ATTR_MAP not valid"}, status=401) 'password': password,
'use_ssl': use_ssl,
util = LDAPUtil( 'search_ougroup': search_ougroup,
use_settings_config=False, server_uri=host, bind_dn=bind_dn, 'search_filter': search_filter,
password=password, use_ssl=use_ssl, 'attr_map': json.loads(attr_map),
search_ougroup=search_ougroup, search_filter=search_filter, }
attr_map=attr_map return config
)
return util
def post(self, request): def post(self, request):
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
if not serializer.is_valid(): if not serializer.is_valid():
return Response({"error": str(serializer.errors)}, status=401) return Response({"error": str(serializer.errors)}, status=401)
util = self.get_ldap_util(serializer) attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
try:
json.loads(attr_map)
except json.JSONDecodeError:
return Response({"error": _("LDAP attr map not valid")}, status=401)
config = self.get_ldap_config(serializer)
util = LDAPServerUtil(config=config)
try: try:
users = util.search_user_items() users = util.search()
except Exception as e: except Exception as e:
return Response({"error": str(e)}, status=401) return Response({"error": str(e)}, status=401)
if len(users) > 0: return Response({"msg": _("Match {} s users").format(len(users))})
return Response({"msg": _("Match {} s users").format(len(users))})
else:
return Response({"error": "Have user but attr mapping error"}, status=401)
class LDAPUserListApi(generics.ListAPIView): class LDAPUserListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsSuperUser,)
serializer_class = LDAPUserSerializer serializer_class = LDAPUserSerializer
def get_queryset_from_cache(self):
search_value = self.request.query_params.get('search')
users = LDAPCacheUtil().search(search_value=search_value)
return users
def get_queryset_from_server(self):
search_value = self.request.query_params.get('search')
users = LDAPServerUtil().search(search_value=search_value)
return users
def get_queryset(self): def get_queryset(self):
if hasattr(self, 'swagger_fake_view'): if hasattr(self, 'swagger_fake_view'):
return [] return []
q = self.request.query_params.get('search') cache_police = self.request.query_params.get('cache_police', True)
try: if cache_police in LDAP_USE_CACHE_FLAGS:
util = LDAPUtil() users = self.get_queryset_from_cache()
extra_filter = util.construct_extra_filter(util.SEARCH_FIELD_ALL, q) else:
users = util.search_user_items(extra_filter) users = self.get_queryset_from_server()
except Exception as e:
users = []
logger.error(e)
# 前端data_table会根据row.id对table.selected值进行操作
for user in users:
user['id'] = user['username']
return users return users
@staticmethod
def processing_queryset(queryset):
db_username_list = User.objects.all().values_list('username', flat=True)
for q in queryset:
q['id'] = q['username']
q['existing'] = q['username'] in db_username_list
return queryset
def sort_queryset(self, queryset): def sort_queryset(self, queryset):
order_by = self.request.query_params.get('order') order_by = self.request.query_params.get('order')
if not order_by: if not order_by:
...@@ -138,32 +160,89 @@ class LDAPUserListApi(generics.ListAPIView): ...@@ -138,32 +160,89 @@ class LDAPUserListApi(generics.ListAPIView):
queryset = sorted(queryset, key=lambda x: x[order_by], reverse=reverse) queryset = sorted(queryset, key=lambda x: x[order_by], reverse=reverse)
return queryset return queryset
def list(self, request, *args, **kwargs): def filter_queryset(self, queryset):
queryset = self.get_queryset() if queryset is None:
return queryset
queryset = self.processing_queryset(queryset)
queryset = self.sort_queryset(queryset) queryset = self.sort_queryset(queryset)
page = self.paginate_queryset(queryset) return queryset
if page is not None:
return self.get_paginated_response(page)
return Response(queryset)
def list(self, request, *args, **kwargs):
cache_police = self.request.query_params.get('cache_police', True)
# 不是用缓存
if cache_police not in LDAP_USE_CACHE_FLAGS:
return super().list(request, *args, **kwargs)
class LDAPUserSyncAPI(APIView): try:
permission_classes = (IsOrgAdmin,) queryset = self.get_queryset()
except Exception as e:
data = {'error': str(e)}
return Response(data=data, status=400)
# 缓存有数据
if queryset is not None:
return super().list(request, *args, **kwargs)
sync_util = LDAPSyncUtil()
# 还没有同步任务
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)}
return Response(data=data, status=409)
# 同步任务正在执行
if sync_util.task_is_running:
data = {'msg': 'synchronization is running.'}
return Response(data=data, status=409)
# 同步任务执行结束
if sync_util.task_is_over:
msg = sync_util.get_task_error_msg()
data = {'error': 'Synchronization task report error: {}'.format(msg)}
return Response(data=data, status=400)
return super().list(request, *args, **kwargs)
class LDAPUserImportAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request): def get_ldap_users(self):
username_list = request.data.get('username_list', []) username_list = self.request.data.get('username_list', [])
cache_police = self.request.query_params.get('cache_police', True)
if cache_police in LDAP_USE_CACHE_FLAGS:
users = LDAPCacheUtil().search(search_users=username_list)
else:
users = LDAPServerUtil().search(search_users=username_list)
return users
util = LDAPUtil() def post(self, request):
try: try:
result = util.sync_users(username_list) users = self.get_ldap_users()
except Exception as e: except Exception as e:
logger.error(e, exc_info=True)
return Response({'error': str(e)}, status=401) return Response({'error': str(e)}, status=401)
else:
msg = _("succeed: {} failed: {} total: {}").format( if users is None:
result['succeed'], result['failed'], result['total'] return Response({'msg': _('Get ldap users is None')}, status=401)
)
return Response({'msg': msg}) errors = LDAPImportUtil().perform_import(users)
if errors:
return Response({'errors': errors}, status=401)
count = users if users is None else len(users)
return Response({'msg': _('Imported {} users successfully').format(count)})
class LDAPCacheRefreshAPI(generics.RetrieveAPIView):
permission_classes = (IsSuperUser,)
def retrieve(self, request, *args, **kwargs):
try:
LDAPSyncUtil().clear_cache()
except Exception as e:
logger.error(str(e))
return Response(data={'msg': str(e)}, status=400)
return Response(data={'msg': 'success'})
class ReplayStorageCreateAPI(APIView): class ReplayStorageCreateAPI(APIView):
...@@ -245,3 +324,19 @@ class CommandStorageDeleteAPI(APIView): ...@@ -245,3 +324,19 @@ class CommandStorageDeleteAPI(APIView):
storage_name = str(request.data.get('name')) storage_name = str(request.data.get('name'))
Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name) Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name)
return Response({"msg": _('Delete succeed')}, status=200) return Response({"msg": _('Delete succeed')}, status=200)
class PublicSettingApi(generics.RetrieveAPIView):
permission_classes = ()
serializer_class = PublicSettingSerializer
def get_object(self):
c = settings.CONFIG
instance = {
"data": {
"WINDOWS_SKIP_ALL_MANUAL_PASSWORD": c.WINDOWS_SKIP_ALL_MANUAL_PASSWORD
}
}
return instance
...@@ -25,6 +25,10 @@ class LDAPTestSerializer(serializers.Serializer): ...@@ -25,6 +25,10 @@ class LDAPTestSerializer(serializers.Serializer):
class LDAPUserSerializer(serializers.Serializer): class LDAPUserSerializer(serializers.Serializer):
id = serializers.CharField() id = serializers.CharField()
username = serializers.CharField() username = serializers.CharField()
name = serializers.CharField()
email = serializers.CharField() email = serializers.CharField()
existing = serializers.BooleanField(read_only=True) existing = serializers.BooleanField(read_only=True)
class PublicSettingSerializer(serializers.Serializer):
data = serializers.DictField(read_only=True)
# coding: utf-8
#
from .ldap import *
# coding: utf-8
#
from celery import shared_task
from common.utils import get_logger
from ..utils import LDAPSyncUtil
__all__ = ['sync_ldap_user_task']
logger = get_logger(__file__)
@shared_task
def sync_ldap_user_task():
LDAPSyncUtil().perform_sync()
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
<div class="row"> <div class="row">
<div class="col-lg-12 animated fadeInRight" id="split-right"> <div class="col-lg-12 animated fadeInRight" id="split-right">
<div class="mail-box-header"> <div class="mail-box-header">
<div class="uc pull-left m-r-5"><a id="id_refresh_cache" class="btn btn-sm btn-primary"> {% trans "Refresh cache" %} </a></div>
<table class="table table-striped table-bordered table-hover " id="ldap_list_users_table" style="width: 100%"> <table class="table table-striped table-bordered table-hover " id="ldap_list_users_table" style="width: 100%">
<thead> <thead>
<tr> <tr>
...@@ -36,6 +37,9 @@ ...@@ -36,6 +37,9 @@
<tbody> <tbody>
</tbody> </tbody>
</table> </table>
<div id="fake_datatable_wrapper_loading" class="dataTables_wrapper" style="display: block;">
<div id="ldap_list_users_table_processing" class="dataTables_processing panel panel-default">{% trans 'Loading' %}...</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -43,8 +47,11 @@ ...@@ -43,8 +47,11 @@
<script> <script>
var ldap_users_table = 0; var ldap_users_table = 0;
var interval;
function initLdapUsersTable() { function initLdapUsersTable() {
if(ldap_users_table){ if(ldap_users_table){
ldap_users_table.ajax.reload(null, false);
return ldap_users_table return ldap_users_table
} }
var options = { var options = {
...@@ -68,21 +75,93 @@ function initLdapUsersTable() { ...@@ -68,21 +75,93 @@ function initLdapUsersTable() {
], ],
pageLength: 15 pageLength: 15
}; };
ldap_users_table = jumpserver.initServerSideDataTable(options); ldap_users_table = jumpserver.initServerSideDataTable(options);
return ldap_users_table return ldap_users_table
} }
function testRequestLdapUser(){
$("#fake_datatable_wrapper_loading").css('display', 'block');
var the_url = "{% url 'api-settings:ldap-user-list' %}";
var error = function (data, status) {
if (status === 409){
console.log(data);
return
}
if (status === 400){
toastr.error(data);
$("#fake_datatable_wrapper_loading").css('display', 'none');
clearInterval(interval);
interval = undefined;
return
}
console.log(data, status)
};
var success = function() {
$("#fake_datatable_wrapper_loading").css('display', 'none');
initLdapUsersTable();
clearInterval(interval);
interval = undefined
};
requestApi({
url: the_url,
method: 'GET',
flash_message: false,
error: error,
success: success
});
}
function timingTestRequestLdapUser(){
if (interval !== undefined){
return
}
interval = setInterval(testRequestLdapUser, 2000);
}
$(document).ready(function(){ $(document).ready(function(){
}).on('show.bs.modal', function () { }).on('show.bs.modal', function () {
initLdapUsersTable(); timingTestRequestLdapUser()
}) })
.on('click','.close_btn1',function () { .on('click', '#id_refresh_cache', function () {
window.location.reload() var the_url = "{% url "api-settings:ldap-cache-refresh" %}";
function error(data) {
toastr.error(data)
}
function success(){
timingTestRequestLdapUser();
}
requestApi({
url: the_url,
method: 'GET',
error: error,
success: success
})
}) })
.on('click','.close_btn2',function () { .on("click","#btn_ldap_modal_confirm",function () {
window.location.reload() var username_list = ldap_users_table.selected;
if (username_list.length === 0){
var msg = "{% trans 'User is not currently selected, please check the user you want to import'%}";
toastr.error(msg);
return
}
var the_url = "{% url "api-settings:ldap-user-import" %}";
function error(message) {
toastr.error(message)
}
function success(message) {
toastr.success(message.msg);
ldap_users_table.selected = [];
timingTestRequestLdapUser();
}
requestApi({
url: the_url,
body: JSON.stringify({'username_list':username_list}),
method: "POST",
flash_message: false,
success: success,
error: error
});
}) })
</script> </script>
......
...@@ -63,9 +63,8 @@ ...@@ -63,9 +63,8 @@
<div class="col-sm-4 col-sm-offset-2"> <div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button> <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button class="btn btn-default btn-test" type="button"> {% trans 'Test connection' %}</button> <button class="btn btn-default btn-test" type="button"> {% trans 'Test connection' %}</button>
{# <button class="btn btn-primary sync_button " data-toggle="modal" data-target="#sync_users_modal" type="button">{% trans 'Synchronization' %}</button>#}
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
<button class="btn btn-default sync_button " data-toggle="modal" data-target="#ldap_list_users_modal" type="button">{% trans 'Bulk import' %}</button> <button class="btn btn-default sync_button " data-toggle="modal" data-target="#ldap_list_users_modal" type="button">{% trans 'Bulk import' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div> </div>
</div> </div>
</form> </form>
...@@ -109,33 +108,6 @@ $(document).ready(function () { ...@@ -109,33 +108,6 @@ $(document).ready(function () {
error: error error: error
}); });
}) })
.on("click","#btn_ldap_modal_confirm",function () {
var username_list = ldap_users_table.selected;
if (username_list.length === 0){
var msg = "{% trans 'User is not currently selected, please check the user you want to import'%}";
toastr.error(msg);
return
}
var the_url = "{% url "api-settings:ldap-user-sync" %}";
function error(message) {
toastr.error(message)
}
function success(message) {
toastr.success(message.msg)
}
requestApi({
url: the_url,
body: JSON.stringify({'username_list':username_list}),
method: "POST",
flash_message: false,
success: success,
error: error
});
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -10,9 +10,11 @@ urlpatterns = [ ...@@ -10,9 +10,11 @@ urlpatterns = [
path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'), path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'),
path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'), path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
path('ldap/users/', api.LDAPUserListApi.as_view(), name='ldap-user-list'), path('ldap/users/', api.LDAPUserListApi.as_view(), name='ldap-user-list'),
path('ldap/users/sync/', api.LDAPUserSyncAPI.as_view(), name='ldap-user-sync'), path('ldap/users/import/', api.LDAPUserImportAPI.as_view(), name='ldap-user-import'),
path('ldap/cache/refresh/', api.LDAPCacheRefreshAPI.as_view(), name='ldap-cache-refresh'),
path('terminal/replay-storage/create/', api.ReplayStorageCreateAPI.as_view(), name='replay-storage-create'), path('terminal/replay-storage/create/', api.ReplayStorageCreateAPI.as_view(), name='replay-storage-create'),
path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'), path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'),
path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'), path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'),
path('terminal/command-storage/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'), path('terminal/command-storage/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'),
path('public/', api.PublicSettingApi.as_view(), name='public-setting'),
] ]
# -*- coding: utf-8 -*-
#
from ldap3 import Server, Connection
from django.utils.translation import ugettext_lazy as _
from users.models import User
from users.utils import construct_user_email
from common.utils import get_logger
from common.const import LDAP_AD_ACCOUNT_DISABLE
from .models import settings
logger = get_logger(__file__)
class LDAPOUGroupException(Exception):
pass
class LDAPUtil:
_conn = None
SEARCH_FIELD_ALL = 'all'
SEARCH_FIELD_USERNAME = 'username'
def __init__(self, use_settings_config=True, server_uri=None, bind_dn=None,
password=None, use_ssl=None, search_ougroup=None,
search_filter=None, attr_map=None, auth_ldap=None):
# config
self.paged_size = settings.AUTH_LDAP_SEARCH_PAGED_SIZE
if use_settings_config:
self._load_config_from_settings()
else:
self.server_uri = server_uri
self.bind_dn = bind_dn
self.password = password
self.use_ssl = use_ssl
self.search_ougroup = search_ougroup
self.search_filter = search_filter
self.attr_map = attr_map
self.auth_ldap = auth_ldap
def _load_config_from_settings(self):
self.server_uri = settings.AUTH_LDAP_SERVER_URI
self.bind_dn = settings.AUTH_LDAP_BIND_DN
self.password = settings.AUTH_LDAP_BIND_PASSWORD
self.use_ssl = settings.AUTH_LDAP_START_TLS
self.search_ougroup = settings.AUTH_LDAP_SEARCH_OU
self.search_filter = settings.AUTH_LDAP_SEARCH_FILTER
self.attr_map = settings.AUTH_LDAP_USER_ATTR_MAP
self.auth_ldap = settings.AUTH_LDAP
@property
def connection(self):
if self._conn is None:
server = Server(self.server_uri, use_ssl=self.use_ssl)
conn = Connection(server, self.bind_dn, self.password)
conn.bind()
self._conn = conn
return self._conn
@staticmethod
def get_user_by_username(username):
try:
user = User.objects.get(username=username)
except Exception as e:
return None
else:
return user
def _ldap_entry_to_user_item(self, entry):
user_item = {}
for attr, mapping in self.attr_map.items():
if not hasattr(entry, mapping):
continue
value = getattr(entry, mapping).value or ''
if mapping.lower() == 'useraccountcontrol' and attr == 'is_active'\
and value:
value = int(value) & LDAP_AD_ACCOUNT_DISABLE \
!= LDAP_AD_ACCOUNT_DISABLE
user_item[attr] = value
return user_item
def _search_user_items_ou(self, search_ou, extra_filter=None, cookie=None):
search_filter = self.search_filter % {"user": "*"}
if extra_filter:
search_filter = '(&{}{})'.format(search_filter, extra_filter)
ok = self.connection.search(
search_ou, search_filter,
attributes=list(self.attr_map.values()),
paged_size=self.paged_size, paged_cookie=cookie
)
if not ok:
error = _("Search no entry matched in ou {}".format(search_ou))
raise LDAPOUGroupException(error)
user_items = []
for entry in self.connection.entries:
user_item = self._ldap_entry_to_user_item(entry)
user = self.get_user_by_username(user_item['username'])
user_item['existing'] = bool(user)
if user_item in user_items:
continue
user_items.append(user_item)
return user_items
def _cookie(self):
if self.paged_size is None:
cookie = None
else:
cookie = self.connection.result['controls']['1.2.840.113556.1.4.319']['value']['cookie']
return cookie
def search_user_items(self, extra_filter=None):
user_items = []
logger.info("Search user items")
for search_ou in str(self.search_ougroup).split("|"):
logger.info("Search user search ou: {}".format(search_ou))
_user_items = self._search_user_items_ou(search_ou, extra_filter=extra_filter)
user_items.extend(_user_items)
while self._cookie():
logger.info("Page Search user search ou: {}".format(search_ou))
_user_items = self._search_user_items_ou(search_ou, extra_filter, self._cookie())
user_items.extend(_user_items)
logger.info("Search user items end")
return user_items
def construct_extra_filter(self, field, q):
if not q:
return None
extra_filter = ''
if field == self.SEARCH_FIELD_ALL:
for attr in self.attr_map.values():
extra_filter += '({}={})'.format(attr, q)
extra_filter = '(|{})'.format(extra_filter)
return extra_filter
if field == self.SEARCH_FIELD_USERNAME and isinstance(q, list):
attr = self.attr_map.get('username')
for username in q:
extra_filter += '({}={})'.format(attr, username)
extra_filter = '(|{})'.format(extra_filter)
return extra_filter
def search_filter_user_items(self, username_list):
extra_filter = self.construct_extra_filter(
self.SEARCH_FIELD_USERNAME, username_list
)
user_items = self.search_user_items(extra_filter)
return user_items
@staticmethod
def save_user(user, user_item):
for field, value in user_item.items():
if not hasattr(user, field):
continue
if isinstance(getattr(user, field), bool):
if isinstance(value, str):
value = value.lower()
value = value in ['true', 1, True]
setattr(user, field, value)
user.save()
def update_user(self, user_item):
user = self.get_user_by_username(user_item['username'])
if user.source != User.SOURCE_LDAP:
msg = _('The user source is not LDAP')
return False, msg
try:
self.save_user(user, user_item)
except Exception as e:
logger.error(e, exc_info=True)
return False, str(e)
else:
return True, None
def create_user(self, user_item):
user = User(source=User.SOURCE_LDAP)
try:
self.save_user(user, user_item)
except Exception as e:
logger.error(e, exc_info=True)
return False, str(e)
else:
return True, None
@staticmethod
def construct_user_email(user_item):
username = user_item['username']
email = user_item.get('email', '')
email = construct_user_email(username, email)
return email
def create_or_update_users(self, user_items):
succeed = failed = 0
for user_item in user_items:
exist = user_item.pop('existing', False)
user_item['email'] = self.construct_user_email(user_item)
if not exist:
ok, error = self.create_user(user_item)
else:
ok, error = self.update_user(user_item)
if not ok:
logger.info("Failed User: {}".format(user_item))
failed += 1
else:
succeed += 1
result = {'total': len(user_items), 'succeed': succeed, 'failed': failed}
return result
def sync_users(self, username_list=None):
user_items = self.search_filter_user_items(username_list)
result = self.create_or_update_users(user_items)
return result
# coding: utf-8
#
from .ldap import *
# coding: utf-8
#
from ldap3 import Server, Connection
from django.conf import settings
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from common.const import LDAP_AD_ACCOUNT_DISABLE
from common.utils import timeit, get_logger
from users.utils import construct_user_email
from users.models import User
logger = get_logger(__file__)
__all__ = [
'LDAPConfig', 'LDAPServerUtil', 'LDAPCacheUtil', 'LDAPImportUtil',
'LDAPSyncUtil', 'LDAP_USE_CACHE_FLAGS'
]
LDAP_USE_CACHE_FLAGS = [1, '1', 'true', 'True', True]
class LDAPOUGroupException(Exception):
pass
class LDAPConfig(object):
def __init__(self, config=None):
self.server_uri = None
self.bind_dn = None
self.password = None
self.use_ssl = None
self.search_ougroup = None
self.search_filter = None
self.attr_map = None
if isinstance(config, dict):
self.load_from_config(config)
else:
self.load_from_settings()
def load_from_config(self, config):
self.server_uri = config.get('server_uri')
self.bind_dn = config.get('bind_dn')
self.password = config.get('password')
self.use_ssl = config.get('use_ssl')
self.search_ougroup = config.get('search_ougroup')
self.search_filter = config.get('search_filter')
self.attr_map = config.get('attr_map')
def load_from_settings(self):
self.server_uri = settings.AUTH_LDAP_SERVER_URI
self.bind_dn = settings.AUTH_LDAP_BIND_DN
self.password = settings.AUTH_LDAP_BIND_PASSWORD
self.use_ssl = settings.AUTH_LDAP_START_TLS
self.search_ougroup = settings.AUTH_LDAP_SEARCH_OU
self.search_filter = settings.AUTH_LDAP_SEARCH_FILTER
self.attr_map = settings.AUTH_LDAP_USER_ATTR_MAP
class LDAPServerUtil(object):
def __init__(self, config=None):
if isinstance(config, dict):
self.config = LDAPConfig(config=config)
elif isinstance(config, LDAPConfig):
self.config = config
else:
self.config = LDAPConfig()
self._conn = None
self._paged_size = self.get_paged_size()
self.search_users = None
self.search_value = None
@property
def connection(self):
if self._conn:
return self._conn
server = Server(self.config.server_uri, use_ssl=self.config.use_ssl)
conn = Connection(server, self.config.bind_dn, self.config.password)
conn.bind()
self._conn = conn
return self._conn
@staticmethod
def get_paged_size():
paged_size = settings.AUTH_LDAP_SEARCH_PAGED_SIZE
if isinstance(paged_size, int):
return paged_size
return None
def paged_cookie(self):
if self._paged_size is None:
return None
cookie = self.connection.result['controls']['1.2.840.113556.1.4.319']['value']['cookie']
return cookie
def get_search_filter_extra(self):
extra = ''
if self.search_users:
mapping_username = self.config.attr_map.get('username')
for user in self.search_users:
extra += '({}={})'.format(mapping_username, user)
return '(|{})'.format(extra)
if self.search_value:
for attr in self.config.attr_map.values():
extra += '({}={})'.format(attr, self.search_value)
return '(|{})'.format(extra)
return extra
def get_search_filter(self):
search_filter = self.config.search_filter % {'user': '*'}
search_filter_extra = self.get_search_filter_extra()
if search_filter_extra:
search_filter = '(&{}{})'.format(search_filter, search_filter_extra)
return search_filter
def search_user_entries_ou(self, search_ou, paged_cookie=None):
search_filter = self.get_search_filter()
attributes = list(self.config.attr_map.values())
ok = self.connection.search(
search_base=search_ou, search_filter=search_filter,
attributes=attributes, paged_size=self._paged_size,
paged_cookie=paged_cookie
)
if not ok:
error = _("Search no entry matched in ou {}".format(search_ou))
raise LDAPOUGroupException(error)
@timeit
def search_user_entries(self):
logger.info("Search user entries")
user_entries = list()
search_ous = str(self.config.search_ougroup).split('|')
for search_ou in search_ous:
logger.info("Search user entries ou: {}".format(search_ou))
self.search_user_entries_ou(search_ou)
user_entries.extend(self.connection.entries)
while self.paged_cookie():
self.search_user_entries_ou(search_ou, self.paged_cookie())
user_entries.extend(self.connection.entries)
return user_entries
def user_entry_to_dict(self, entry):
user = {}
attr_map = self.config.attr_map.items()
for attr, mapping in attr_map:
if not hasattr(entry, mapping):
continue
value = getattr(entry, mapping).value or ''
if attr == 'is_active' and mapping.lower() == 'useraccountcontrol' \
and value:
value = int(value) & LDAP_AD_ACCOUNT_DISABLE != LDAP_AD_ACCOUNT_DISABLE
user[attr] = value
return user
@timeit
def user_entries_to_dict(self, user_entries):
users = []
for user_entry in user_entries:
user = self.user_entry_to_dict(user_entry)
users.append(user)
return users
@timeit
def search(self, search_users=None, search_value=None):
logger.info("Search ldap users")
self.search_users = search_users
self.search_value = search_value
user_entries = self.search_user_entries()
users = self.user_entries_to_dict(user_entries)
return users
class LDAPCacheUtil(object):
CACHE_KEY_USERS = 'CACHE_KEY_LDAP_USERS'
def __init__(self):
self.search_users = None
self.search_value = None
def set_users(self, users):
logger.info('Set ldap users to cache, count: {}'.format(len(users)))
cache.set(self.CACHE_KEY_USERS, users, None)
def get_users(self):
users = cache.get(self.CACHE_KEY_USERS)
count = users if users is None else len(users)
logger.info('Get ldap users from cache, count: {}'.format(count))
return users
def delete_users(self):
logger.info('Delete ldap users from cache')
cache.delete(self.CACHE_KEY_USERS)
def filter_users(self, users):
if users is None:
return users
if self.search_users:
filter_users = [
user for user in users
if user['username'] in self.search_users
]
elif self.search_value:
filter_users = [
user for user in users
if self.search_value in ','.join(user.values())
]
else:
filter_users = users
return filter_users
def search(self, search_users=None, search_value=None):
self.search_users = search_users
self.search_value = search_value
users = self.get_users()
users = self.filter_users(users)
return users
class LDAPSyncUtil(object):
CACHE_KEY_LDAP_USERS_SYNC_TASK_ERROR_MSG = 'CACHE_KEY_LDAP_USERS_SYNC_TASK_ERROR_MSG'
CACHE_KEY_LDAP_USERS_SYNC_TASK_STATUS = 'CACHE_KEY_LDAP_USERS_SYNC_TASK_STATUS'
TASK_STATUS_IS_RUNNING = 'RUNNING'
TASK_STATUS_IS_OVER = 'OVER'
def __init__(self):
self.server_util = LDAPServerUtil()
self.cache_util = LDAPCacheUtil()
self.task_error_msg = None
def clear_cache(self):
logger.info('Clear ldap sync cache')
self.delete_task_status()
self.delete_task_error_msg()
self.cache_util.delete_users()
@property
def task_no_start(self):
status = self.get_task_status()
return status is None
@property
def task_is_running(self):
status = self.get_task_status()
return status == self.TASK_STATUS_IS_RUNNING
@property
def task_is_over(self):
status = self.get_task_status()
return status == self.TASK_STATUS_IS_OVER
def set_task_status(self, status):
logger.info('Set task status: {}'.format(status))
cache.set(self.CACHE_KEY_LDAP_USERS_SYNC_TASK_STATUS, status, None)
def get_task_status(self):
status = cache.get(self.CACHE_KEY_LDAP_USERS_SYNC_TASK_STATUS)
logger.info('Get task status: {}'.format(status))
return status
def delete_task_status(self):
logger.info('Delete task status')
cache.delete(self.CACHE_KEY_LDAP_USERS_SYNC_TASK_STATUS)
def set_task_error_msg(self, error_msg):
logger.info('Set task error msg')
cache.set(self.CACHE_KEY_LDAP_USERS_SYNC_TASK_ERROR_MSG, error_msg, None)
def get_task_error_msg(self):
logger.info('Get task error msg')
error_msg = cache.get(self.CACHE_KEY_LDAP_USERS_SYNC_TASK_ERROR_MSG)
return error_msg
def delete_task_error_msg(self):
logger.info('Delete task error msg')
cache.delete(self.CACHE_KEY_LDAP_USERS_SYNC_TASK_ERROR_MSG)
def pre_sync(self):
self.set_task_status(self.TASK_STATUS_IS_RUNNING)
def sync(self):
users = self.server_util.search()
self.cache_util.set_users(users)
def post_sync(self):
self.set_task_status(self.TASK_STATUS_IS_OVER)
def perform_sync(self):
logger.info('Start perform sync ldap users from server to cache')
self.pre_sync()
try:
self.sync()
except Exception as e:
error_msg = str(e)
logger.error(error_msg)
self.set_task_error_msg(error_msg)
self.post_sync()
logger.info('End perform sync ldap users from server to cache')
class LDAPImportUtil(object):
def __init__(self):
pass
@staticmethod
def get_user_email(user):
username = user['username']
email = user['email']
email = construct_user_email(username, email)
return email
def update_or_create(self, user):
user['email'] = self.get_user_email(user)
if user['username'] not in ['admin']:
user['source'] = User.SOURCE_LDAP
obj, created = User.objects.update_or_create(
username=user['username'], defaults=user
)
return obj, created
def perform_import(self, users):
logger.info('Start perform import ldap users, count: {}'.format(len(users)))
errors = []
for user in users:
try:
self.update_or_create(user)
except Exception as e:
errors.append({user['username']: str(e)})
logger.error(e)
logger.info('End perform import ldap users')
return errors
...@@ -5,6 +5,7 @@ from django.utils.translation import ugettext as _ ...@@ -5,6 +5,7 @@ from django.utils.translation import ugettext as _
from common.permissions import PermissionsMixin, IsSuperUser from common.permissions import PermissionsMixin, IsSuperUser
from common import utils from common import utils
from .utils import LDAPSyncUtil
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm
...@@ -83,6 +84,7 @@ class LDAPSettingView(PermissionsMixin, TemplateView): ...@@ -83,6 +84,7 @@ class LDAPSettingView(PermissionsMixin, TemplateView):
form.save() form.save()
msg = _("Update setting successfully") msg = _("Update setting successfully")
messages.success(request, msg) messages.success(request, msg)
LDAPSyncUtil().clear_cache()
return redirect('settings:ldap-setting') return redirect('settings:ldap-setting')
else: else:
context = self.get_context_data() context = self.get_context_data()
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.conf import settings
from common.utils import validate_ssh_public_key from common.utils import validate_ssh_public_key
from orgs.mixins.forms import OrgModelForm from orgs.mixins.forms import OrgModelForm
...@@ -21,6 +22,20 @@ class UserCheckOtpCodeForm(forms.Form): ...@@ -21,6 +22,20 @@ class UserCheckOtpCodeForm(forms.Form):
otp_code = forms.CharField(label=_('MFA code'), max_length=6) otp_code = forms.CharField(label=_('MFA code'), max_length=6)
def get_source_choices():
choices_all = dict(User.SOURCE_CHOICES)
choices = [
(User.SOURCE_LOCAL, choices_all[User.SOURCE_LOCAL]),
]
if settings.AUTH_LDAP:
choices.append((User.SOURCE_LDAP, choices_all[User.SOURCE_LDAP]))
if settings.AUTH_OPENID:
choices.append((User.SOURCE_OPENID, choices_all[User.SOURCE_OPENID]))
if settings.AUTH_RADIUS:
choices.append((User.SOURCE_RADIUS, choices_all[User.SOURCE_RADIUS]))
return choices
class UserCreateUpdateFormMixin(OrgModelForm): class UserCreateUpdateFormMixin(OrgModelForm):
role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP) role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP)
password = forms.CharField( password = forms.CharField(
...@@ -31,6 +46,10 @@ class UserCreateUpdateFormMixin(OrgModelForm): ...@@ -31,6 +46,10 @@ class UserCreateUpdateFormMixin(OrgModelForm):
choices=role_choices, required=True, choices=role_choices, required=True,
initial=User.ROLE_USER, label=_("Role") initial=User.ROLE_USER, label=_("Role")
) )
source = forms.ChoiceField(
choices=get_source_choices, required=True,
initial=User.SOURCE_LOCAL, label=_("Source")
)
public_key = forms.CharField( public_key = forms.CharField(
label=_('ssh public key'), max_length=5000, required=False, label=_('ssh public key'), max_length=5000, required=False,
widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}), widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}),
...@@ -41,7 +60,8 @@ class UserCreateUpdateFormMixin(OrgModelForm): ...@@ -41,7 +60,8 @@ class UserCreateUpdateFormMixin(OrgModelForm):
model = User model = User
fields = [ fields = [
'username', 'name', 'email', 'groups', 'wechat', 'username', 'name', 'email', 'groups', 'wechat',
'phone', 'role', 'date_expired', 'comment', 'otp_level' 'source', 'phone', 'role', 'date_expired',
'comment', 'otp_level'
] ]
widgets = { widgets = {
'otp_level': forms.RadioSelect(), 'otp_level': forms.RadioSelect(),
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from .v1 import * from .user import *
\ No newline at end of file from .group import *
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.fields import StringManyToManyField
from common.serializers import AdaptedBulkListSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import User, UserGroup
from .. import utils
__all__ = [
'UserGroupSerializer', 'UserGroupListSerializer',
'UserGroupUpdateMemberSerializer'
]
class UserGroupSerializer(BulkOrgResourceModelSerializer):
users = serializers.PrimaryKeyRelatedField(
required=False, many=True, queryset=User.objects, label=_('User')
)
class Meta:
model = UserGroup
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'users', 'comment', 'date_created',
'created_by',
]
extra_kwargs = {
'created_by': {'label': _('Created by'), 'read_only': True}
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_fields_queryset()
def set_fields_queryset(self):
users_field = self.fields['users']
users_field.child_relation.queryset = utils.get_current_org_members()
def validate_users(self, users):
for user in users:
if user.is_super_auditor:
msg = _('Auditors cannot be join in the user group')
raise serializers.ValidationError(msg)
return users
class UserGroupListSerializer(UserGroupSerializer):
users = StringManyToManyField(many=True, read_only=True)
class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects)
class Meta:
model = UserGroup
fields = ['id', 'users']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_fields_queryset()
def set_fields_queryset(self):
users_field = self.fields['users']
users_field.child_relation.queryset = utils.get_current_org_members()
...@@ -6,19 +6,14 @@ from rest_framework import serializers ...@@ -6,19 +6,14 @@ from rest_framework import serializers
from common.utils import validate_ssh_public_key from common.utils import validate_ssh_public_key
from common.mixins import BulkSerializerMixin from common.mixins import BulkSerializerMixin
from common.fields import StringManyToManyField
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from common.permissions import CanUpdateDeleteUser from common.permissions import CanUpdateDeleteUser
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import User, UserGroup from ..models import User, UserGroup
from .. import utils
__all__ = [ __all__ = [
'UserSerializer', 'UserPKUpdateSerializer', 'UserUpdateGroupSerializer', 'UserSerializer', 'UserPKUpdateSerializer', 'UserUpdateGroupSerializer',
'UserGroupSerializer', 'UserGroupListSerializer', 'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
'UserGroupUpdateMemberSerializer', 'ChangeUserPasswordSerializer',
'ResetOTPSerializer',
] ]
...@@ -49,7 +44,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -49,7 +44,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
'is_valid': {'label': _('Is valid')}, 'is_valid': {'label': _('Is valid')},
'is_expired': {'label': _('Is expired')}, 'is_expired': {'label': _('Is expired')},
'avatar_url': {'label': _('Avatar url')}, 'avatar_url': {'label': _('Avatar url')},
'source': {'read_only': True},
'created_by': {'read_only': True, 'allow_blank': True}, 'created_by': {'read_only': True, 'allow_blank': True},
'can_update': {'read_only': True}, 'can_update': {'read_only': True},
'can_delete': {'read_only': True}, 'can_delete': {'read_only': True},
...@@ -127,58 +121,6 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer): ...@@ -127,58 +121,6 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer):
fields = ['id', 'groups'] fields = ['id', 'groups']
class UserGroupSerializer(BulkOrgResourceModelSerializer):
users = serializers.PrimaryKeyRelatedField(
required=False, many=True, queryset=User.objects, label=_('User')
)
class Meta:
model = UserGroup
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'users', 'comment', 'date_created',
'created_by',
]
extra_kwargs = {
'created_by': {'label': _('Created by'), 'read_only': True}
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_fields_queryset()
def set_fields_queryset(self):
users_field = self.fields['users']
users_field.child_relation.queryset = utils.get_current_org_members()
def validate_users(self, users):
for user in users:
if user.is_super_auditor:
msg = _('Auditors cannot be join in the user group')
raise serializers.ValidationError(msg)
return users
class UserGroupListSerializer(UserGroupSerializer):
users = StringManyToManyField(many=True, read_only=True)
class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects)
class Meta:
model = UserGroup
fields = ['id', 'users']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_fields_queryset()
def set_fields_queryset(self):
users_field = self.fields['users']
users_field.child_relation.queryset = utils.get_current_org_members()
class ChangeUserPasswordSerializer(serializers.ModelSerializer): class ChangeUserPasswordSerializer(serializers.ModelSerializer):
class Meta: class Meta:
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
# #
from django.dispatch import receiver from django.dispatch import receiver
# from django.db.models.signals import post_save from django.db.models.signals import post_save, m2m_changed
from common.utils import get_logger from common.utils import get_logger
from .signals import post_user_create from .signals import post_user_create
# from .models import User from .models import User
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -28,3 +28,14 @@ def on_user_create(sender, user=None, **kwargs): ...@@ -28,3 +28,14 @@ def on_user_create(sender, user=None, **kwargs):
logger.info(" - Sending welcome mail ...".format(user.name)) logger.info(" - Sending welcome mail ...".format(user.name))
if user.email: if user.email:
send_user_created_mail(user) send_user_created_mail(user)
@receiver(m2m_changed, sender=User.groups.through)
def on_user_groups_change(sender, instance=None, action='', **kwargs):
"""
资产节点发生变化时,刷新节点
"""
if action.startswith('post'):
logger.debug("User group member change signal recv: {}".format(instance))
from perms.utils import AssetPermissionUtilV2
AssetPermissionUtilV2.expire_all_user_tree_cache()
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import sys
from celery import shared_task from celery import shared_task
from django.conf import settings from django.conf import settings
from ops.celery.utils import create_or_update_celery_periodic_tasks from ops.celery.utils import (
create_or_update_celery_periodic_tasks, disable_celery_periodic_task
)
from ops.celery.decorator import after_app_ready_start from ops.celery.decorator import after_app_ready_start
from common.utils import get_logger from common.utils import get_logger
from .models import User from .models import User
from .utils import ( from .utils import (
send_password_expiration_reminder_mail, send_user_expiration_reminder_mail send_password_expiration_reminder_mail, send_user_expiration_reminder_mail
) )
from settings.utils import LDAPUtil from settings.utils import LDAPServerUtil, LDAPImportUtil
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -70,19 +73,26 @@ def check_user_expired_periodic(): ...@@ -70,19 +73,26 @@ def check_user_expired_periodic():
@shared_task @shared_task
def sync_ldap_user(): def import_ldap_user():
logger.info("Start sync ldap user periodic task") logger.info("Start import ldap user task")
util = LDAPUtil() util_server = LDAPServerUtil()
result = util.sync_users() util_import = LDAPImportUtil()
logger.info("Result: {}".format(result)) users = util_server.search()
errors = util_import.perform_import(users)
if errors:
logger.error("Imported LDAP users errors: {}".format(errors))
else:
logger.info('Imported {} users successfully'.format(len(users)))
@shared_task @shared_task
@after_app_ready_start @after_app_ready_start
def sync_ldap_user_periodic(): def import_ldap_user_periodic():
if not settings.AUTH_LDAP: if not settings.AUTH_LDAP:
return return
if not settings.AUTH_LDAP_SYNC_IS_PERIODIC: if not settings.AUTH_LDAP_SYNC_IS_PERIODIC:
task_name = sys._getframe().f_code.co_name
disable_celery_periodic_task(task_name)
return return
interval = settings.AUTH_LDAP_SYNC_INTERVAL interval = settings.AUTH_LDAP_SYNC_INTERVAL
...@@ -91,10 +101,9 @@ def sync_ldap_user_periodic(): ...@@ -91,10 +101,9 @@ def sync_ldap_user_periodic():
else: else:
interval = None interval = None
crontab = settings.AUTH_LDAP_SYNC_CRONTAB crontab = settings.AUTH_LDAP_SYNC_CRONTAB
tasks = { tasks = {
'sync_ldap_user_periodic': { 'import_ldap_user_periodic': {
'task': sync_ldap_user.name, 'task': import_ldap_user.name,
'interval': interval, 'interval': interval,
'crontab': crontab, 'crontab': crontab,
'enabled': True, 'enabled': True,
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
<h3>{% trans 'Auth' %}</h3> <h3>{% trans 'Auth' %}</h3>
{% block password %}{% endblock %} {% block password %}{% endblock %}
{% bootstrap_field form.otp_level layout="horizontal" %} {% bootstrap_field form.otp_level layout="horizontal" %}
{% bootstrap_field form.source layout="horizontal" %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans 'Security and Role' %}</h3> <h3>{% trans 'Security and Role' %}</h3>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment