mirror of https://github.com/jumpserver/jumpserver
commit
3f1e75c6f9
|
@ -6,6 +6,7 @@ import time
|
||||||
|
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import ugettext
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
@ -67,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:
|
||||||
|
@ -336,6 +337,17 @@ class SomeNodesMixin:
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_next_org_root_node_key(cls):
|
||||||
|
with tmp_to_org(Organization.root()):
|
||||||
|
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||||
|
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
|
||||||
|
if not org_nodes_roots_keys:
|
||||||
|
org_nodes_roots_keys = ['1']
|
||||||
|
max_key = max([int(k) for k in org_nodes_roots_keys])
|
||||||
|
key = str(max_key + 1) if max_key != 0 else '2'
|
||||||
|
return key
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_org_root_node(cls):
|
def create_org_root_node(cls):
|
||||||
# 如果使用current_org 在set_current_org时会死循环
|
# 如果使用current_org 在set_current_org时会死循环
|
||||||
|
@ -343,14 +355,7 @@ class SomeNodesMixin:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if not ori_org.is_real():
|
if not ori_org.is_real():
|
||||||
return cls.default_node()
|
return cls.default_node()
|
||||||
set_current_org(Organization.root())
|
key = cls.get_next_org_root_node_key()
|
||||||
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
|
||||||
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
|
|
||||||
if not org_nodes_roots_keys:
|
|
||||||
org_nodes_roots_keys = ['1']
|
|
||||||
key = max([int(k) for k in org_nodes_roots_keys])
|
|
||||||
key = str(key + 1) if key != 0 else '2'
|
|
||||||
set_current_org(ori_org)
|
|
||||||
root = cls.objects.create(key=key, value=ori_org.name)
|
root = cls.objects.create(key=key, value=ori_org.name)
|
||||||
return root
|
return root
|
||||||
|
|
||||||
|
@ -384,9 +389,16 @@ class SomeNodesMixin:
|
||||||
def default_node(cls):
|
def default_node(cls):
|
||||||
with tmp_to_org(Organization.default()):
|
with tmp_to_org(Organization.default()):
|
||||||
defaults = {'value': cls.default_value}
|
defaults = {'value': cls.default_value}
|
||||||
obj, created = cls.objects.get_or_create(
|
try:
|
||||||
defaults=defaults, key=cls.default_key,
|
obj, created = cls.objects.get_or_create(
|
||||||
)
|
defaults=defaults, key=cls.default_key,
|
||||||
|
)
|
||||||
|
except IntegrityError as e:
|
||||||
|
logger.error("Create default node failed: {}".format(e))
|
||||||
|
cls.modify_other_org_root_node_key()
|
||||||
|
obj, created = cls.objects.get_or_create(
|
||||||
|
defaults=defaults, key=cls.default_key,
|
||||||
|
)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -405,6 +417,35 @@ class SomeNodesMixin:
|
||||||
cls.ungrouped_node()
|
cls.ungrouped_node()
|
||||||
cls.favorite_node()
|
cls.favorite_node()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def modify_other_org_root_node_key(cls):
|
||||||
|
"""
|
||||||
|
解决创建 default 节点失败的问题,
|
||||||
|
因为在其他组织下存在 default 节点,故在 DEFAULT 组织下 get 不到 create 失败
|
||||||
|
"""
|
||||||
|
logger.info("Modify other org root node key")
|
||||||
|
|
||||||
|
with tmp_to_org(Organization.root()):
|
||||||
|
node_key1 = cls.objects.filter(key='1').first()
|
||||||
|
if not node_key1:
|
||||||
|
logger.info("Not found node that `key` = 1")
|
||||||
|
return
|
||||||
|
if not node_key1.org.is_real():
|
||||||
|
logger.info("Org is not real for node that `key` = 1")
|
||||||
|
return
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
with tmp_to_org(node_key1.org):
|
||||||
|
org_root_node_new_key = cls.get_next_org_root_node_key()
|
||||||
|
for n in cls.objects.all():
|
||||||
|
old_key = n.key
|
||||||
|
key_list = n.key.split(':')
|
||||||
|
key_list[0] = org_root_node_new_key
|
||||||
|
new_key = ':'.join(key_list)
|
||||||
|
n.key = new_key
|
||||||
|
n.save()
|
||||||
|
logger.info('Modify key ( {} > {} )'.format(old_key, new_key))
|
||||||
|
|
||||||
|
|
||||||
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
|
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
|
|
|
@ -426,13 +426,15 @@ $(document).ready(function(){
|
||||||
function success(data) {
|
function success(data) {
|
||||||
url = setUrlParam(the_url, 'spm', data.spm);
|
url = setUrlParam(the_url, 'spm', data.spm);
|
||||||
requestApi({
|
requestApi({
|
||||||
url:url,
|
url: url,
|
||||||
method:'DELETE',
|
method: 'DELETE',
|
||||||
success:refreshPage,
|
success: function () {
|
||||||
flash_message:false,
|
var msg = "{% trans 'Asset Deleted.' %}";
|
||||||
|
swal("{% trans 'Asset Delete' %}", msg, "success");
|
||||||
|
refreshPage();
|
||||||
|
},
|
||||||
|
flash_message: false,
|
||||||
});
|
});
|
||||||
var msg = "{% trans 'Asset Deleted.' %}";
|
|
||||||
swal("{% trans 'Asset Delete' %}", msg, "success");
|
|
||||||
}
|
}
|
||||||
function fail() {
|
function fail() {
|
||||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
var msg = "{% trans 'Asset Deleting failed.' %}";
|
||||||
|
@ -440,10 +442,11 @@ $(document).ready(function(){
|
||||||
}
|
}
|
||||||
requestApi({
|
requestApi({
|
||||||
url: "{% url 'api-common:resources-cache' %}",
|
url: "{% url 'api-common:resources-cache' %}",
|
||||||
method:'POST',
|
method: 'POST',
|
||||||
body:JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
success:success,
|
success: success,
|
||||||
error:fail
|
error: fail,
|
||||||
|
flash_message: false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,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()
|
||||||
|
|
||||||
|
|
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-11-08 17:27+0800\n"
|
"POT-Creation-Date: 2019-11-12 14:10+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"
|
||||||
|
@ -96,7 +96,7 @@ msgstr "运行参数"
|
||||||
#: terminal/templates/terminal/session_list.html:28
|
#: terminal/templates/terminal/session_list.html:28
|
||||||
#: terminal/templates/terminal/session_list.html:72
|
#: terminal/templates/terminal/session_list.html:72
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:73
|
#: xpack/plugins/change_auth_plan/forms.py:73
|
||||||
#: xpack/plugins/change_auth_plan/models.py:412
|
#: xpack/plugins/change_auth_plan/models.py:419
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
|
||||||
|
@ -137,14 +137,14 @@ 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
|
||||||
#: settings/templates/settings/terminal_setting.html:105 terminal/models.py:23
|
#: settings/templates/settings/terminal_setting.html:105 terminal/models.py:23
|
||||||
#: terminal/models.py:260 terminal/templates/terminal/terminal_detail.html:43
|
#: terminal/models.py:260 terminal/templates/terminal/terminal_detail.html:43
|
||||||
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
|
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
|
||||||
#: users/models/user.py:382 users/templates/users/_select_user_modal.html:13
|
#: users/models/user.py:410 users/templates/users/_select_user_modal.html:13
|
||||||
#: users/templates/users/user_detail.html:64
|
#: users/templates/users/user_detail.html:64
|
||||||
#: users/templates/users/user_group_detail.html:55
|
#: users/templates/users/user_group_detail.html:55
|
||||||
#: users/templates/users/user_group_list.html:35
|
#: users/templates/users/user_group_list.html:35
|
||||||
|
@ -152,7 +152,7 @@ msgstr "资产"
|
||||||
#: users/templates/users/user_profile.html:51
|
#: users/templates/users/user_profile.html:51
|
||||||
#: users/templates/users/user_pubkey_update.html:57
|
#: users/templates/users/user_pubkey_update.html:57
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:56
|
#: xpack/plugins/change_auth_plan/forms.py:56
|
||||||
#: xpack/plugins/change_auth_plan/models.py:63
|
#: xpack/plugins/change_auth_plan/models.py:64
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
|
||||||
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:144
|
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:144
|
||||||
|
@ -197,9 +197,9 @@ 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:423 users/serializers/group.py:32
|
#: users/models/user.py:451 users/serializers/group.py:32
|
||||||
#: users/templates/users/user_detail.html:112
|
#: users/templates/users/user_detail.html:112
|
||||||
#: xpack/plugins/change_auth_plan/models.py:108
|
#: xpack/plugins/change_auth_plan/models.py:109
|
||||||
#: 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
|
||||||
#: xpack/plugins/cloud/models.py:80 xpack/plugins/cloud/models.py:179
|
#: xpack/plugins/cloud/models.py:80 xpack/plugins/cloud/models.py:179
|
||||||
#: xpack/plugins/gathered_user/models.py:46
|
#: xpack/plugins/gathered_user/models.py:46
|
||||||
|
@ -260,11 +260,11 @@ msgstr "创建日期"
|
||||||
#: settings/models.py:34 terminal/models.py:33
|
#: settings/models.py:34 terminal/models.py:33
|
||||||
#: terminal/templates/terminal/terminal_detail.html:63
|
#: terminal/templates/terminal/terminal_detail.html:63
|
||||||
#: tickets/templates/tickets/ticket_detail.html:106 users/models/group.py:15
|
#: tickets/templates/tickets/ticket_detail.html:106 users/models/group.py:15
|
||||||
#: users/models/user.py:415 users/templates/users/user_detail.html:130
|
#: users/models/user.py:443 users/templates/users/user_detail.html:130
|
||||||
#: users/templates/users/user_group_detail.html:67
|
#: users/templates/users/user_group_detail.html:67
|
||||||
#: users/templates/users/user_group_list.html:37
|
#: users/templates/users/user_group_list.html:37
|
||||||
#: users/templates/users/user_profile.html:138
|
#: users/templates/users/user_profile.html:138
|
||||||
#: xpack/plugins/change_auth_plan/models.py:104
|
#: xpack/plugins/change_auth_plan/models.py:105
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
|
||||||
#: xpack/plugins/cloud/models.py:77 xpack/plugins/cloud/models.py:173
|
#: xpack/plugins/cloud/models.py:77 xpack/plugins/cloud/models.py:173
|
||||||
|
@ -529,7 +529,7 @@ msgstr "创建远程应用"
|
||||||
#: terminal/templates/terminal/session_list.html:36
|
#: terminal/templates/terminal/session_list.html:36
|
||||||
#: terminal/templates/terminal/terminal_list.html:36
|
#: terminal/templates/terminal/terminal_list.html:36
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:18
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:18
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:105
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:106
|
||||||
#: users/templates/users/_granted_assets.html:34
|
#: users/templates/users/_granted_assets.html:34
|
||||||
#: users/templates/users/user_group_list.html:38
|
#: users/templates/users/user_group_list.html:38
|
||||||
#: users/templates/users/user_list.html:41
|
#: users/templates/users/user_list.html:41
|
||||||
|
@ -607,7 +607,7 @@ msgstr "端口"
|
||||||
#: assets/templates/assets/asset_detail.html:196
|
#: assets/templates/assets/asset_detail.html:196
|
||||||
#: assets/templates/assets/system_user_assets.html:83
|
#: assets/templates/assets/system_user_assets.html:83
|
||||||
#: perms/models/asset_permission.py:81
|
#: perms/models/asset_permission.py:81
|
||||||
#: xpack/plugins/change_auth_plan/models.py:74
|
#: xpack/plugins/change_auth_plan/models.py:75
|
||||||
#: xpack/plugins/gathered_user/models.py:31
|
#: xpack/plugins/gathered_user/models.py:31
|
||||||
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17
|
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17
|
||||||
msgid "Nodes"
|
msgid "Nodes"
|
||||||
|
@ -639,7 +639,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
|
||||||
|
@ -704,18 +704,18 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
|
||||||
#: assets/templates/assets/system_user_list.html:48 audits/models.py:81
|
#: 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:58
|
#: authentication/templates/authentication/login.html:58
|
||||||
#: authentication/templates/authentication/xpack_login.html:91
|
#: authentication/templates/authentication/xpack_login.html:93
|
||||||
#: 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:14
|
#: settings/templates/settings/_ldap_list_users_modal.html:31 users/forms.py:14
|
||||||
#: users/models/user.py:380 users/templates/users/_select_user_modal.html:14
|
#: users/models/user.py:408 users/templates/users/_select_user_modal.html:14
|
||||||
#: users/templates/users/user_detail.html:68
|
#: users/templates/users/user_detail.html:68
|
||||||
#: users/templates/users/user_list.html:36
|
#: users/templates/users/user_list.html:36
|
||||||
#: users/templates/users/user_profile.html:47
|
#: users/templates/users/user_profile.html:47
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:58
|
#: xpack/plugins/change_auth_plan/forms.py:58
|
||||||
#: xpack/plugins/change_auth_plan/models.py:65
|
#: xpack/plugins/change_auth_plan/models.py:66
|
||||||
#: xpack/plugins/change_auth_plan/models.py:408
|
#: xpack/plugins/change_auth_plan/models.py:415
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12
|
||||||
|
@ -734,7 +734,7 @@ msgstr "密码或密钥密码"
|
||||||
#: assets/templates/assets/_asset_user_auth_view_modal.html:27
|
#: assets/templates/assets/_asset_user_auth_view_modal.html:27
|
||||||
#: authentication/forms.py:15
|
#: authentication/forms.py:15
|
||||||
#: authentication/templates/authentication/login.html:66
|
#: authentication/templates/authentication/login.html:66
|
||||||
#: authentication/templates/authentication/xpack_login.html:99
|
#: authentication/templates/authentication/xpack_login.html:101
|
||||||
#: settings/forms.py:114 users/forms.py:16 users/forms.py:42
|
#: 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
|
||||||
|
@ -742,14 +742,14 @@ msgstr "密码或密钥密码"
|
||||||
#: users/templates/users/user_profile_update.html:41
|
#: users/templates/users/user_profile_update.html:41
|
||||||
#: users/templates/users/user_pubkey_update.html:41
|
#: users/templates/users/user_pubkey_update.html:41
|
||||||
#: users/templates/users/user_update.html:20
|
#: users/templates/users/user_update.html:20
|
||||||
#: xpack/plugins/change_auth_plan/models.py:95
|
#: xpack/plugins/change_auth_plan/models.py:96
|
||||||
#: xpack/plugins/change_auth_plan/models.py:263
|
#: xpack/plugins/change_auth_plan/models.py:264
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "密码"
|
msgstr "密码"
|
||||||
|
|
||||||
#: assets/forms/user.py:30 assets/serializers/asset_user.py:71
|
#: assets/forms/user.py:30 assets/serializers/asset_user.py:71
|
||||||
#: assets/templates/assets/_asset_user_auth_update_modal.html:27
|
#: assets/templates/assets/_asset_user_auth_update_modal.html:27
|
||||||
#: users/models/user.py:409
|
#: users/models/user.py:437
|
||||||
msgid "Private key"
|
msgid "Private key"
|
||||||
msgstr "ssh私钥"
|
msgstr "ssh私钥"
|
||||||
|
|
||||||
|
@ -937,13 +937,13 @@ msgstr "版本"
|
||||||
msgid "AuthBook"
|
msgid "AuthBook"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/models/base.py:31 xpack/plugins/change_auth_plan/models.py:99
|
#: assets/models/base.py:31 xpack/plugins/change_auth_plan/models.py:100
|
||||||
#: xpack/plugins/change_auth_plan/models.py:270
|
#: xpack/plugins/change_auth_plan/models.py:271
|
||||||
msgid "SSH private key"
|
msgid "SSH private key"
|
||||||
msgstr "ssh密钥"
|
msgstr "ssh密钥"
|
||||||
|
|
||||||
#: assets/models/base.py:32 xpack/plugins/change_auth_plan/models.py:102
|
#: assets/models/base.py:32 xpack/plugins/change_auth_plan/models.py:103
|
||||||
#: xpack/plugins/change_auth_plan/models.py:266
|
#: xpack/plugins/change_auth_plan/models.py:267
|
||||||
msgid "SSH public key"
|
msgid "SSH public key"
|
||||||
msgstr "ssh公钥"
|
msgstr "ssh公钥"
|
||||||
|
|
||||||
|
@ -963,7 +963,7 @@ msgstr "带宽"
|
||||||
msgid "Contact"
|
msgid "Contact"
|
||||||
msgstr "联系人"
|
msgstr "联系人"
|
||||||
|
|
||||||
#: assets/models/cluster.py:22 users/models/user.py:401
|
#: assets/models/cluster.py:22 users/models/user.py:429
|
||||||
#: users/templates/users/user_detail.html:77
|
#: users/templates/users/user_detail.html:77
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "手机"
|
msgstr "手机"
|
||||||
|
@ -989,7 +989,7 @@ msgid "Default"
|
||||||
msgstr "默认"
|
msgstr "默认"
|
||||||
|
|
||||||
#: assets/models/cluster.py:36 assets/models/label.py:14
|
#: assets/models/cluster.py:36 assets/models/label.py:14
|
||||||
#: users/models/user.py:521
|
#: users/models/user.py:549
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "系统"
|
msgstr "系统"
|
||||||
|
|
||||||
|
@ -1120,8 +1120,9 @@ msgstr "默认资产组"
|
||||||
#: terminal/templates/terminal/session_list.html:71 tickets/models/base.py:25
|
#: terminal/templates/terminal/session_list.html:71 tickets/models/base.py:25
|
||||||
#: tickets/models/base.py:68
|
#: tickets/models/base.py:68
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:15
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:15
|
||||||
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:101
|
||||||
#: tickets/templates/tickets/ticket_detail.html:32 users/forms.py:339
|
#: tickets/templates/tickets/ticket_detail.html:32 users/forms.py:339
|
||||||
#: users/models/user.py:132 users/models/user.py:148 users/models/user.py:509
|
#: users/models/user.py:149 users/models/user.py:165 users/models/user.py:537
|
||||||
#: users/serializers/group.py:21
|
#: users/serializers/group.py:21
|
||||||
#: users/templates/users/user_group_detail.html:78
|
#: 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
|
||||||
|
@ -1131,7 +1132,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 "值"
|
||||||
|
@ -1140,23 +1141,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 "键"
|
||||||
|
|
||||||
|
@ -1187,7 +1188,7 @@ msgstr "手动登录"
|
||||||
#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73
|
#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73
|
||||||
#: assets/views/system_user.py:29 assets/views/system_user.py:46
|
#: assets/views/system_user.py:29 assets/views/system_user.py:46
|
||||||
#: assets/views/system_user.py:63 assets/views/system_user.py:79
|
#: assets/views/system_user.py:63 assets/views/system_user.py:79
|
||||||
#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:70
|
#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:71
|
||||||
msgid "Assets"
|
msgid "Assets"
|
||||||
msgstr "资产管理"
|
msgstr "资产管理"
|
||||||
|
|
||||||
|
@ -1277,7 +1278,7 @@ msgid "Backend"
|
||||||
msgstr "后端"
|
msgstr "后端"
|
||||||
|
|
||||||
#: assets/serializers/asset_user.py:67 users/forms.py:282
|
#: assets/serializers/asset_user.py:67 users/forms.py:282
|
||||||
#: users/models/user.py:412 users/templates/users/first_login.html:42
|
#: users/models/user.py:440 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
|
||||||
#: users/templates/users/user_profile_update.html:46
|
#: users/templates/users/user_profile_update.html:46
|
||||||
|
@ -1332,7 +1333,7 @@ msgstr "测试资产可连接性: {}"
|
||||||
|
|
||||||
#: assets/tasks/asset_user_connectivity.py:27
|
#: assets/tasks/asset_user_connectivity.py:27
|
||||||
#: assets/tasks/push_system_user.py:130
|
#: assets/tasks/push_system_user.py:130
|
||||||
#: xpack/plugins/change_auth_plan/models.py:521
|
#: xpack/plugins/change_auth_plan/models.py:528
|
||||||
msgid "The asset {} system platform {} does not support run Ansible tasks"
|
msgid "The asset {} system platform {} does not support run Ansible tasks"
|
||||||
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
|
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
|
||||||
|
|
||||||
|
@ -1450,6 +1451,7 @@ msgstr "资产列表"
|
||||||
#: assets/templates/assets/_node_tree.html:39
|
#: 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 @@ msgid "Asset user auth"
|
||||||
msgstr "资产用户信息"
|
msgstr "资产用户信息"
|
||||||
|
|
||||||
#: assets/templates/assets/_asset_user_auth_view_modal.html:54
|
#: assets/templates/assets/_asset_user_auth_view_modal.html:54
|
||||||
#: authentication/templates/authentication/login_wait_confirm.html:115
|
#: authentication/templates/authentication/login_wait_confirm.html:114
|
||||||
msgid "Copy success"
|
msgid "Copy success"
|
||||||
msgstr "复制成功"
|
msgstr "复制成功"
|
||||||
|
|
||||||
|
@ -1493,7 +1495,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 tickets/models/base.py:50
|
#: templates/_modal.html:22 tickets/models/base.py:50
|
||||||
#: tickets/templates/tickets/ticket_detail.html:103
|
#: tickets/templates/tickets/ticket_detail.html:103
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
|
@ -1667,7 +1669,6 @@ msgstr "选择节点"
|
||||||
#: assets/templates/assets/system_user_detail.html:182
|
#: assets/templates/assets/system_user_detail.html:182
|
||||||
#: assets/templates/assets/system_user_list.html:135
|
#: assets/templates/assets/system_user_list.html:135
|
||||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:20
|
#: authentication/templates/authentication/_mfa_confirm_modal.html:20
|
||||||
#: authentication/templates/authentication/login_wait_confirm.html:136
|
|
||||||
#: settings/templates/settings/terminal_setting.html:168
|
#: settings/templates/settings/terminal_setting.html:168
|
||||||
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112
|
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112
|
||||||
#: users/templates/users/user_detail.html:271
|
#: users/templates/users/user_detail.html:271
|
||||||
|
@ -1713,7 +1714,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
|
||||||
|
@ -1899,16 +1900,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 "删除失败"
|
||||||
|
|
||||||
|
@ -2281,13 +2282,13 @@ msgstr "Agent"
|
||||||
|
|
||||||
#: audits/models.py:86 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:194 users/models/user.py:404
|
#: users/forms.py:194 users/models/user.py:432
|
||||||
#: 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:87 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:423
|
||||||
#: 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
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
|
||||||
|
@ -2296,7 +2297,7 @@ msgstr "原因"
|
||||||
|
|
||||||
#: audits/models.py:88 audits/templates/audits/login_log_list.html:64
|
#: audits/models.py:88 audits/templates/audits/login_log_list.html:64
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:16
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:16
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:101
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:102
|
||||||
#: tickets/templates/tickets/ticket_detail.html:34
|
#: tickets/templates/tickets/ticket_detail.html:34
|
||||||
#: 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
|
||||||
|
@ -2316,8 +2317,8 @@ msgstr "登录日期"
|
||||||
#: perms/templates/perms/asset_permission_detail.html:86
|
#: perms/templates/perms/asset_permission_detail.html:86
|
||||||
#: perms/templates/perms/remote_app_permission_detail.html:78
|
#: perms/templates/perms/remote_app_permission_detail.html:78
|
||||||
#: terminal/models.py:167 terminal/templates/terminal/session_list.html:34
|
#: terminal/models.py:167 terminal/templates/terminal/session_list.html:34
|
||||||
#: xpack/plugins/change_auth_plan/models.py:249
|
#: xpack/plugins/change_auth_plan/models.py:250
|
||||||
#: xpack/plugins/change_auth_plan/models.py:419
|
#: xpack/plugins/change_auth_plan/models.py:426
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17
|
||||||
#: xpack/plugins/gathered_user/models.py:143
|
#: xpack/plugins/gathered_user/models.py:143
|
||||||
|
@ -2557,14 +2558,14 @@ msgid "Show"
|
||||||
msgstr "显示"
|
msgstr "显示"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:66
|
#: authentication/templates/authentication/_access_key_modal.html:66
|
||||||
#: users/models/user.py:335 users/templates/users/user_profile.html:94
|
#: users/models/user.py:352 users/templates/users/user_profile.html:94
|
||||||
#: users/templates/users/user_profile.html:163
|
#: users/templates/users/user_profile.html:163
|
||||||
#: users/templates/users/user_profile.html:166
|
#: users/templates/users/user_profile.html:166
|
||||||
msgid "Disable"
|
msgid "Disable"
|
||||||
msgstr "禁用"
|
msgstr "禁用"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:67
|
#: authentication/templates/authentication/_access_key_modal.html:67
|
||||||
#: users/models/user.py:336 users/templates/users/user_profile.html:92
|
#: users/models/user.py:353 users/templates/users/user_profile.html:92
|
||||||
#: users/templates/users/user_profile.html:170
|
#: users/templates/users/user_profile.html:170
|
||||||
msgid "Enable"
|
msgid "Enable"
|
||||||
msgstr "启用"
|
msgstr "启用"
|
||||||
|
@ -2626,7 +2627,7 @@ msgstr "改变世界,从一点点开始。"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:45
|
#: authentication/templates/authentication/login.html:45
|
||||||
#: authentication/templates/authentication/login.html:76
|
#: authentication/templates/authentication/login.html:76
|
||||||
#: authentication/templates/authentication/xpack_login.html:110
|
#: authentication/templates/authentication/xpack_login.html:112
|
||||||
#: templates/_header_bar.html:83
|
#: templates/_header_bar.html:83
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "登录"
|
msgstr "登录"
|
||||||
|
@ -2637,7 +2638,7 @@ msgid "Captcha invalid"
|
||||||
msgstr "验证码错误"
|
msgstr "验证码错误"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:87
|
#: authentication/templates/authentication/login.html:87
|
||||||
#: authentication/templates/authentication/xpack_login.html:114
|
#: authentication/templates/authentication/xpack_login.html:116
|
||||||
#: users/templates/users/forgot_password.html:10
|
#: users/templates/users/forgot_password.html:10
|
||||||
#: users/templates/users/forgot_password.html:25
|
#: users/templates/users/forgot_password.html:25
|
||||||
msgid "Forgot password"
|
msgid "Forgot password"
|
||||||
|
@ -2701,11 +2702,11 @@ msgstr "返回"
|
||||||
msgid "Welcome back, please enter username and password to login"
|
msgid "Welcome back, please enter username and password to login"
|
||||||
msgstr "欢迎回来,请输入用户名和密码登录"
|
msgstr "欢迎回来,请输入用户名和密码登录"
|
||||||
|
|
||||||
#: authentication/views/login.py:71
|
#: authentication/views/login.py:70
|
||||||
msgid "Please enable cookies and try again."
|
msgid "Please enable cookies and try again."
|
||||||
msgstr "设置你的浏览器支持cookie"
|
msgstr "设置你的浏览器支持cookie"
|
||||||
|
|
||||||
#: authentication/views/login.py:170
|
#: authentication/views/login.py:157
|
||||||
msgid ""
|
msgid ""
|
||||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||||
" Don't close this page"
|
" Don't close this page"
|
||||||
|
@ -2713,15 +2714,15 @@ msgstr ""
|
||||||
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
||||||
" 不要关闭本页面"
|
" 不要关闭本页面"
|
||||||
|
|
||||||
#: authentication/views/login.py:175
|
#: authentication/views/login.py:162
|
||||||
msgid "No ticket found"
|
msgid "No ticket found"
|
||||||
msgstr "没有发现工单"
|
msgstr "没有发现工单"
|
||||||
|
|
||||||
#: authentication/views/login.py:198
|
#: authentication/views/login.py:185
|
||||||
msgid "Logout success"
|
msgid "Logout success"
|
||||||
msgstr "退出登录成功"
|
msgstr "退出登录成功"
|
||||||
|
|
||||||
#: authentication/views/login.py:199
|
#: authentication/views/login.py:186
|
||||||
msgid "Logout success, return login page"
|
msgid "Logout success, return login page"
|
||||||
msgstr "退出登录成功,返回到登录页面"
|
msgstr "退出登录成功,返回到登录页面"
|
||||||
|
|
||||||
|
@ -2905,8 +2906,8 @@ msgstr "完成时间"
|
||||||
|
|
||||||
#: ops/models/adhoc.py:358 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:253
|
||||||
#: xpack/plugins/change_auth_plan/models.py:422
|
#: xpack/plugins/change_auth_plan/models.py:429
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16
|
||||||
#: xpack/plugins/gathered_user/models.py:146
|
#: xpack/plugins/gathered_user/models.py:146
|
||||||
|
@ -3197,7 +3198,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
|
||||||
#: 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:313 users/models/group.py:26
|
#: templates/_nav.html:21 users/forms.py:313 users/models/group.py:26
|
||||||
#: users/models/user.py:388 users/templates/users/_select_user_modal.html:16
|
#: users/models/user.py:416 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
|
||||||
#: xpack/plugins/orgs/templates/orgs/org_list.html:16
|
#: xpack/plugins/orgs/templates/orgs/org_list.html:16
|
||||||
|
@ -3239,7 +3240,7 @@ msgstr "资产授权"
|
||||||
#: perms/models/base.py:53
|
#: perms/models/base.py:53
|
||||||
#: perms/templates/perms/asset_permission_detail.html:90
|
#: perms/templates/perms/asset_permission_detail.html:90
|
||||||
#: perms/templates/perms/remote_app_permission_detail.html:82
|
#: perms/templates/perms/remote_app_permission_detail.html:82
|
||||||
#: users/models/user.py:420 users/templates/users/user_detail.html:108
|
#: users/models/user.py:448 users/templates/users/user_detail.html:108
|
||||||
#: users/templates/users/user_profile.html:120
|
#: users/templates/users/user_profile.html:120
|
||||||
msgid "Date expired"
|
msgid "Date expired"
|
||||||
msgstr "失效日期"
|
msgstr "失效日期"
|
||||||
|
@ -3456,33 +3457,41 @@ msgstr "远程应用授权用户列表"
|
||||||
msgid "RemoteApp permission RemoteApp list"
|
msgid "RemoteApp permission RemoteApp list"
|
||||||
msgstr "远程应用授权远程应用列表"
|
msgstr "远程应用授权远程应用列表"
|
||||||
|
|
||||||
#: settings/api.py:31
|
#: settings/api.py:37
|
||||||
msgid "Test mail sent to {}, please check"
|
msgid "Test mail sent to {}, please check"
|
||||||
msgstr "邮件已经发送{}, 请检查"
|
msgstr "邮件已经发送{}, 请检查"
|
||||||
|
|
||||||
#: settings/api.py:70
|
#: settings/api.py:76
|
||||||
msgid "Test ldap success"
|
msgid "Test ldap success"
|
||||||
msgstr "连接LDAP成功"
|
msgstr "连接LDAP成功"
|
||||||
|
|
||||||
#: settings/api.py:107
|
#: settings/api.py:107
|
||||||
|
msgid "LDAP attr map not valid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: settings/api.py:116
|
||||||
msgid "Match {} s users"
|
msgid "Match {} s users"
|
||||||
msgstr "匹配 {} 个用户"
|
msgstr "匹配 {} 个用户"
|
||||||
|
|
||||||
#: settings/api.py:166
|
#: settings/api.py:224
|
||||||
msgid "succeed: {} failed: {} total: {}"
|
msgid "Get ldap users is None"
|
||||||
msgstr "成功:{} 失败:{} 总数:{}"
|
msgstr "获取 LDAP 用户为 None"
|
||||||
|
|
||||||
#: settings/api.py:188 settings/api.py:224
|
#: 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:194 settings/api.py:230
|
#: settings/api.py:268 settings/api.py:304
|
||||||
msgid "Create succeed"
|
msgid "Create succeed"
|
||||||
msgstr "创建成功"
|
msgstr "创建成功"
|
||||||
|
|
||||||
#: settings/api.py:212 settings/api.py:250
|
#: 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 "删除成功"
|
||||||
|
@ -3802,23 +3811,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
|
||||||
#: users/models/user.py:384 users/templates/users/user_detail.html:72
|
msgid "Refresh cache"
|
||||||
|
msgstr "刷新缓存"
|
||||||
|
|
||||||
|
#: settings/templates/settings/_ldap_list_users_modal.html:33
|
||||||
|
#: users/models/user.py:412 users/templates/users/user_detail.html:72
|
||||||
#: 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 "基本设置"
|
||||||
|
|
||||||
|
@ -3827,7 +3845,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 "邮件设置"
|
||||||
|
|
||||||
|
@ -3836,7 +3854,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 "邮件内容设置"
|
||||||
|
|
||||||
|
@ -3845,7 +3863,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设置"
|
||||||
|
|
||||||
|
@ -3854,7 +3872,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 "终端设置"
|
||||||
|
|
||||||
|
@ -3864,7 +3882,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 "安全设置"
|
||||||
|
|
||||||
|
@ -3884,15 +3902,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 "桶名称"
|
||||||
|
@ -3997,30 +4010,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:180
|
||||||
|
|
||||||
#: 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:180
|
|
||||||
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 "创建命令存储"
|
||||||
|
|
||||||
|
@ -4570,7 +4579,7 @@ msgstr "接受"
|
||||||
#: tickets/models/login_confirm.py:16
|
#: tickets/models/login_confirm.py:16
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_detail.html:10
|
#: tickets/templates/tickets/login_confirm_ticket_detail.html:10
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:70
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:70
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:107
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:108
|
||||||
msgid "Reject"
|
msgid "Reject"
|
||||||
msgstr "拒绝"
|
msgstr "拒绝"
|
||||||
|
|
||||||
|
@ -4608,12 +4617,12 @@ msgid ""
|
||||||
msgstr "你可以使用ssh客户端工具连接终端"
|
msgstr "你可以使用ssh客户端工具连接终端"
|
||||||
|
|
||||||
#: tickets/models/base.py:16 tickets/models/base.py:52
|
#: tickets/models/base.py:16 tickets/models/base.py:52
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:102
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:103
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "开启"
|
msgstr "开启"
|
||||||
|
|
||||||
#: tickets/models/base.py:17
|
#: tickets/models/base.py:17
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:103
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:104
|
||||||
msgid "Closed"
|
msgid "Closed"
|
||||||
msgstr "关闭"
|
msgstr "关闭"
|
||||||
|
|
||||||
|
@ -4658,7 +4667,7 @@ msgstr "{} {} 这个工单"
|
||||||
#: tickets/models/login_confirm.py:15
|
#: tickets/models/login_confirm.py:15
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_detail.html:9
|
#: tickets/templates/tickets/login_confirm_ticket_detail.html:9
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:69
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:69
|
||||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:106
|
#: tickets/templates/tickets/login_confirm_ticket_list.html:107
|
||||||
msgid "Approve"
|
msgid "Approve"
|
||||||
msgstr "同意"
|
msgstr "同意"
|
||||||
|
|
||||||
|
@ -4771,7 +4780,7 @@ msgstr "登录复核工单详情"
|
||||||
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:47 users/models/user.py:392
|
#: users/forms.py:47 users/models/user.py:420
|
||||||
#: users/templates/users/_select_user_modal.html:15
|
#: users/templates/users/_select_user_modal.html:15
|
||||||
#: users/templates/users/user_detail.html:88
|
#: users/templates/users/user_detail.html:88
|
||||||
#: users/templates/users/user_list.html:37
|
#: users/templates/users/user_list.html:37
|
||||||
|
@ -4779,7 +4788,7 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
msgstr "角色"
|
msgstr "角色"
|
||||||
|
|
||||||
#: users/forms.py:51 users/models/user.py:427
|
#: users/forms.py:51 users/models/user.py:455
|
||||||
#: users/templates/users/user_detail.html:104
|
#: users/templates/users/user_detail.html:104
|
||||||
#: users/templates/users/user_list.html:39
|
#: users/templates/users/user_list.html:39
|
||||||
#: users/templates/users/user_profile.html:102
|
#: users/templates/users/user_profile.html:102
|
||||||
|
@ -4823,7 +4832,7 @@ msgstr "生成重置密码链接,通过邮件发送给用户"
|
||||||
msgid "Set password"
|
msgid "Set password"
|
||||||
msgstr "设置密码"
|
msgstr "设置密码"
|
||||||
|
|
||||||
#: users/forms.py:152 xpack/plugins/change_auth_plan/models.py:88
|
#: users/forms.py:152 xpack/plugins/change_auth_plan/models.py:89
|
||||||
#: 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
|
||||||
|
@ -4897,44 +4906,44 @@ msgstr "选择用户"
|
||||||
msgid "User auth from {}, go there change password"
|
msgid "User auth from {}, go there change password"
|
||||||
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
|
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
|
||||||
|
|
||||||
#: users/models/user.py:131 users/models/user.py:517
|
#: users/models/user.py:148 users/models/user.py:545
|
||||||
msgid "Administrator"
|
msgid "Administrator"
|
||||||
msgstr "管理员"
|
msgstr "管理员"
|
||||||
|
|
||||||
#: users/models/user.py:133
|
#: users/models/user.py:150
|
||||||
msgid "Application"
|
msgid "Application"
|
||||||
msgstr "应用程序"
|
msgstr "应用程序"
|
||||||
|
|
||||||
#: users/models/user.py:134 xpack/plugins/orgs/forms.py:30
|
#: users/models/user.py:151 xpack/plugins/orgs/forms.py:30
|
||||||
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
|
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
|
||||||
msgid "Auditor"
|
msgid "Auditor"
|
||||||
msgstr "审计员"
|
msgstr "审计员"
|
||||||
|
|
||||||
#: users/models/user.py:144
|
#: users/models/user.py:161
|
||||||
msgid "Org admin"
|
msgid "Org admin"
|
||||||
msgstr "组织管理员"
|
msgstr "组织管理员"
|
||||||
|
|
||||||
#: users/models/user.py:146
|
#: users/models/user.py:163
|
||||||
msgid "Org auditor"
|
msgid "Org auditor"
|
||||||
msgstr "组织审计员"
|
msgstr "组织审计员"
|
||||||
|
|
||||||
#: users/models/user.py:337 users/templates/users/user_profile.html:90
|
#: users/models/user.py:354 users/templates/users/user_profile.html:90
|
||||||
msgid "Force enable"
|
msgid "Force enable"
|
||||||
msgstr "强制启用"
|
msgstr "强制启用"
|
||||||
|
|
||||||
#: users/models/user.py:395
|
#: users/models/user.py:423
|
||||||
msgid "Avatar"
|
msgid "Avatar"
|
||||||
msgstr "头像"
|
msgstr "头像"
|
||||||
|
|
||||||
#: users/models/user.py:398 users/templates/users/user_detail.html:83
|
#: users/models/user.py:426 users/templates/users/user_detail.html:83
|
||||||
msgid "Wechat"
|
msgid "Wechat"
|
||||||
msgstr "微信"
|
msgstr "微信"
|
||||||
|
|
||||||
#: users/models/user.py:431
|
#: users/models/user.py:459
|
||||||
msgid "Date password last updated"
|
msgid "Date password last updated"
|
||||||
msgstr "最后更新密码日期"
|
msgstr "最后更新密码日期"
|
||||||
|
|
||||||
#: users/models/user.py:520
|
#: users/models/user.py:548
|
||||||
msgid "Administrator is the super user of system"
|
msgid "Administrator is the super user of system"
|
||||||
msgstr "Administrator是初始的超级管理员"
|
msgstr "Administrator是初始的超级管理员"
|
||||||
|
|
||||||
|
@ -5753,8 +5762,8 @@ msgstr ""
|
||||||
"具</a>) <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
|
"具</a>) <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/meta.py:9
|
#: xpack/plugins/change_auth_plan/meta.py:9
|
||||||
#: xpack/plugins/change_auth_plan/models.py:116
|
#: xpack/plugins/change_auth_plan/models.py:117
|
||||||
#: xpack/plugins/change_auth_plan/models.py:256
|
#: xpack/plugins/change_auth_plan/models.py:257
|
||||||
#: xpack/plugins/change_auth_plan/views.py:33
|
#: xpack/plugins/change_auth_plan/views.py:33
|
||||||
#: xpack/plugins/change_auth_plan/views.py:50
|
#: xpack/plugins/change_auth_plan/views.py:50
|
||||||
#: xpack/plugins/change_auth_plan/views.py:74
|
#: xpack/plugins/change_auth_plan/views.py:74
|
||||||
|
@ -5765,20 +5774,20 @@ msgstr ""
|
||||||
msgid "Change auth plan"
|
msgid "Change auth plan"
|
||||||
msgstr "改密计划"
|
msgstr "改密计划"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:57
|
#: xpack/plugins/change_auth_plan/models.py:58
|
||||||
msgid "Custom password"
|
msgid "Custom password"
|
||||||
msgstr "自定义密码"
|
msgstr "自定义密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:58
|
#: xpack/plugins/change_auth_plan/models.py:59
|
||||||
msgid "All assets use the same random password"
|
msgid "All assets use the same random password"
|
||||||
msgstr "所有资产使用相同的随机密码"
|
msgstr "所有资产使用相同的随机密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:59
|
#: xpack/plugins/change_auth_plan/models.py:60
|
||||||
msgid "All assets use different random password"
|
msgid "All assets use different random password"
|
||||||
msgstr "所有资产使用不同的随机密码"
|
msgstr "所有资产使用不同的随机密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:78
|
#: xpack/plugins/change_auth_plan/models.py:79
|
||||||
#: xpack/plugins/change_auth_plan/models.py:147
|
#: xpack/plugins/change_auth_plan/models.py:148
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100
|
||||||
#: xpack/plugins/cloud/models.py:165 xpack/plugins/cloud/models.py:219
|
#: xpack/plugins/cloud/models.py:165 xpack/plugins/cloud/models.py:219
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91
|
||||||
|
@ -5787,8 +5796,8 @@ msgstr "所有资产使用不同的随机密码"
|
||||||
msgid "Cycle perform"
|
msgid "Cycle perform"
|
||||||
msgstr "周期执行"
|
msgstr "周期执行"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:83
|
#: xpack/plugins/change_auth_plan/models.py:84
|
||||||
#: xpack/plugins/change_auth_plan/models.py:145
|
#: xpack/plugins/change_auth_plan/models.py:146
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92
|
||||||
#: xpack/plugins/cloud/models.py:170 xpack/plugins/cloud/models.py:217
|
#: xpack/plugins/cloud/models.py:170 xpack/plugins/cloud/models.py:217
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83
|
||||||
|
@ -5797,37 +5806,37 @@ msgstr "周期执行"
|
||||||
msgid "Regularly perform"
|
msgid "Regularly perform"
|
||||||
msgstr "定期执行"
|
msgstr "定期执行"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:92
|
#: xpack/plugins/change_auth_plan/models.py:93
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74
|
||||||
msgid "Password rules"
|
msgid "Password rules"
|
||||||
msgstr "密码规则"
|
msgstr "密码规则"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:212
|
#: xpack/plugins/change_auth_plan/models.py:213
|
||||||
msgid "* For security, do not change {} user's password"
|
msgid "* For security, do not change {} user's password"
|
||||||
msgstr "* 为了安全,禁止更改 {} 用户的密码"
|
msgstr "* 为了安全,禁止更改 {} 用户的密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:216
|
#: xpack/plugins/change_auth_plan/models.py:217
|
||||||
msgid "Assets is empty, please add the asset"
|
msgid "Assets is empty, please add the asset"
|
||||||
msgstr "资产为空,请添加资产"
|
msgstr "资产为空,请添加资产"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:260
|
#: xpack/plugins/change_auth_plan/models.py:261
|
||||||
msgid "Change auth plan snapshot"
|
msgid "Change auth plan snapshot"
|
||||||
msgstr "改密计划快照"
|
msgstr "改密计划快照"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:275
|
#: xpack/plugins/change_auth_plan/models.py:276
|
||||||
#: xpack/plugins/change_auth_plan/models.py:426
|
#: xpack/plugins/change_auth_plan/models.py:433
|
||||||
msgid "Change auth plan execution"
|
msgid "Change auth plan execution"
|
||||||
msgstr "改密计划执行"
|
msgstr "改密计划执行"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:435
|
#: xpack/plugins/change_auth_plan/models.py:442
|
||||||
msgid "Change auth plan execution subtask"
|
msgid "Change auth plan execution subtask"
|
||||||
msgstr "改密计划执行子任务"
|
msgstr "改密计划执行子任务"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:453
|
#: xpack/plugins/change_auth_plan/models.py:460
|
||||||
msgid "Authentication failed"
|
msgid "Authentication failed"
|
||||||
msgstr "认证失败"
|
msgstr "认证失败"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:455
|
#: xpack/plugins/change_auth_plan/models.py:462
|
||||||
msgid "Connection timeout"
|
msgid "Connection timeout"
|
||||||
msgstr "连接超时"
|
msgstr "连接超时"
|
||||||
|
|
||||||
|
@ -6437,6 +6446,12 @@ msgstr "密码匣子"
|
||||||
msgid "vault create"
|
msgid "vault create"
|
||||||
msgstr "创建"
|
msgstr "创建"
|
||||||
|
|
||||||
|
#~ msgid "succeed: {} failed: {} total: {}"
|
||||||
|
#~ msgstr "成功:{} 失败:{} 总数:{}"
|
||||||
|
|
||||||
|
#~ msgid "The user source is not LDAP"
|
||||||
|
#~ msgstr "用户来源不是LDAP"
|
||||||
|
|
||||||
#~ msgid "selected"
|
#~ msgid "selected"
|
||||||
#~ msgstr "所选"
|
#~ msgstr "所选"
|
||||||
|
|
||||||
|
@ -6610,25 +6625,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
|
||||||
|
|
|
@ -12,21 +12,27 @@ from django.conf import settings
|
||||||
from django.core.mail import send_mail
|
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 .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 .models import Setting
|
|
||||||
from .utils import LDAPUtil
|
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
MailTestSerializer, LDAPTestSerializer, LDAPUserSerializer,
|
MailTestSerializer, LDAPTestSerializer, LDAPUserSerializer,
|
||||||
PublicSettingSerializer,
|
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")
|
||||||
|
|
||||||
|
@ -65,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:
|
||||||
|
@ -141,32 +160,87 @@ 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 = 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):
|
||||||
|
|
|
@ -25,6 +25,7 @@ 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
# coding: utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
from .ldap import *
|
|
@ -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 () {
|
.on("click","#btn_ldap_modal_confirm",function () {
|
||||||
window.location.reload()
|
var username_list = ldap_users_table.selected;
|
||||||
|
if (username_list.length === 0){
|
||||||
|
var msg = "{% trans 'User is not currently selected, please check the user you want to import'%}";
|
||||||
|
toastr.error(msg);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var the_url = "{% url "api-settings:ldap-user-import" %}";
|
||||||
|
function error(message) {
|
||||||
|
toastr.error(message)
|
||||||
|
}
|
||||||
|
function success(message) {
|
||||||
|
toastr.success(message.msg);
|
||||||
|
ldap_users_table.selected = [];
|
||||||
|
timingTestRequestLdapUser();
|
||||||
|
}
|
||||||
|
requestApi({
|
||||||
|
url: the_url,
|
||||||
|
body: JSON.stringify({'username_list':username_list}),
|
||||||
|
method: "POST",
|
||||||
|
flash_message: false,
|
||||||
|
success: success,
|
||||||
|
error: error
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -63,9 +63,8 @@
|
||||||
<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,7 +10,8 @@ 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'),
|
||||||
|
|
|
@ -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
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
# coding: utf-8
|
||||||
|
#
|
||||||
|
|
||||||
|
from .ldap import *
|
|
@ -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,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,
|
||||||
|
|
Loading…
Reference in New Issue