mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-30 08:42:04 +00:00
commit
0946645783
@ -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 @@
|
|||||||
# 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):
|
|||||||
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 @@
|
|||||||
|
|
||||||
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):
|
|||||||
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:
|
|||||||
|
|
||||||
@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 @@
|
|||||||
|
|
||||||
<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 @@
|
|||||||
}
|
}
|
||||||
</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) {
|
|||||||
|
|
||||||
|
|
||||||
$(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();
|
||||||
|
@ -428,11 +428,13 @@ $(document).ready(function(){
|
|||||||
requestApi({
|
requestApi({
|
||||||
url: url,
|
url: url,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
success:refreshPage,
|
success: function () {
|
||||||
flash_message:false,
|
|
||||||
});
|
|
||||||
var msg = "{% trans 'Asset Deleted.' %}";
|
var msg = "{% trans 'Asset Deleted.' %}";
|
||||||
swal("{% trans 'Asset Delete' %}", msg, "success");
|
swal("{% trans 'Asset Delete' %}", msg, "success");
|
||||||
|
refreshPage();
|
||||||
|
},
|
||||||
|
flash_message: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function fail() {
|
function fail() {
|
||||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
var msg = "{% trans 'Asset Deleting failed.' %}";
|
||||||
@ -443,7 +445,8 @@ $(document).ready(function(){
|
|||||||
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(){
|
|||||||
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):
|
|||||||
|
|
||||||
@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 = {
|
|||||||
'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
|
|||||||
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
|
||||||
|
Binary file not shown.
@ -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 "运行参数"
|
|||||||
#: 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 "资产"
|
|||||||
#: 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 "参数"
|
|||||||
#: 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 "远程应用"
|
|||||||
#: 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 "重置"
|
|||||||
#: 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 "详情"
|
|||||||
#: 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 "更新"
|
|||||||
#: 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 "创建远程应用"
|
|||||||
#: 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"
|
|||||||
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 "选择资产"
|
|||||||
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"
|
|||||||
#: 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 "密码或密钥密码"
|
|||||||
#: 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 "资产组"
|
|||||||
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 "默认资产组"
|
|||||||
#: 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 "默认资产组"
|
|||||||
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 "值"
|
|||||||
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"
|
|||||||
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 "组织名称"
|
|||||||
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"
|
|||||||
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 "获取认证信息错误"
|
|||||||
#: 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端口"
|
|||||||
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 "导出"
|
|||||||
#: 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 "删除选择资产"
|
|||||||
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 "测试连接"
|
|||||||
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 "资产管理"
|
|||||||
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 "系统用户资产"
|
|||||||
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 "文件名"
|
|||||||
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"
|
|||||||
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 ""
|
|||||||
"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"
|
|||||||
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 "完成时间"
|
|||||||
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 协议不支持单独控制上传或下载文件"
|
|||||||
#: 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 "远程应用授权用户列表"
|
|||||||
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 用户列表"
|
|||||||
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 "基本设置"
|
|||||||
#: 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 "邮件设置"
|
|||||||
#: 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 "邮件内容设置"
|
|||||||
#: 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设置"
|
|||||||
#: 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 "终端设置"
|
|||||||
#: 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 "文档类型"
|
|||||||
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 "删除失败"
|
|||||||
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"
|
|||||||
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客户端工具连接终端"
|
|||||||
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, 请去个人信息页面重置"
|
|||||||
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 "设置密码"
|
|||||||
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 ""
|
|||||||
"启用之后您将会在下次登录时进入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 ""
|
|||||||
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
|
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
|
||||||
"设置复杂密码,启用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 "头像"
|
|||||||
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 "最后更新密码日期"
|
|||||||
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 "选择用户"
|
|||||||
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 "密码匣子"
|
|||||||
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 "创建"
|
|||||||
#~ 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
|
|||||||
|
|
||||||
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
|
|||||||
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):
|
|||||||
|
|
||||||
|
|
||||||
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:
|
try:
|
||||||
users = util.search_user_items()
|
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:
|
||||||
|
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):
|
|||||||
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)
|
def list(self, request, *args, **kwargs):
|
||||||
return Response(queryset)
|
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)
|
||||||
|
|
||||||
|
try:
|
||||||
|
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 LDAPUserSyncAPI(APIView):
|
class LDAPUserImportAPI(APIView):
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsSuperUser,)
|
||||||
|
|
||||||
|
def get_ldap_users(self):
|
||||||
|
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
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
username_list = request.data.get('username_list', [])
|
|
||||||
|
|
||||||
util = LDAPUtil()
|
|
||||||
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):
|
|||||||
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):
|
|||||||
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)
|
||||||
|
4
apps/settings/tasks/__init__.py
Normal file
4
apps/settings/tasks/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
from .ldap import *
|
17
apps/settings/tasks/ldap.py
Normal file
17
apps/settings/tasks/ldap.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# 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 @@
|
|||||||
<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 @@
|
|||||||
<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 @@
|
|||||||
|
|
||||||
<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() {
|
|||||||
],
|
],
|
||||||
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 () {
|
})
|
||||||
window.location.reload()
|
.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-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 @@
|
|||||||
<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 () {
|
|||||||
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 = [
|
|||||||
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'),
|
||||||
]
|
]
|
||||||
|
@ -1,219 +0,0 @@
|
|||||||
# -*- 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
|
|
4
apps/settings/utils/__init__.py
Normal file
4
apps/settings/utils/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
from .ldap import *
|
338
apps/settings/utils/ldap.py
Normal file
338
apps/settings/utils/ldap.py
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
# 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 _
|
|||||||
|
|
||||||
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):
|
|||||||
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 @@
|
|||||||
|
|
||||||
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):
|
|||||||
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):
|
|||||||
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):
|
|||||||
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(),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from .v1 import *
|
from .user import *
|
||||||
|
from .group import *
|
||||||
|
69
apps/users/serializers/group.py
Normal file
69
apps/users/serializers/group.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# -*- 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
|
|||||||
|
|
||||||
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):
|
|||||||
'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):
|
|||||||
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 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
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):
|
|||||||
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()
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
# -*- 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():
|
|||||||
|
|
||||||
|
|
||||||
@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():
|
|||||||
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 @@
|
|||||||
<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>
|
||||||
|
Loading…
Reference in New Issue
Block a user