@@ -306,6 +305,7 @@ function defaultCallback(action) {
$(document).ready(function () {
+ $('.treebox').css('height', window.innerHeight - 180);
})
.on('click', '.btn-show-current-asset', function(){
hideRMenu();
@@ -322,4 +322,4 @@ $(document).ready(function () {
location.reload();
})
-
\ No newline at end of file
+
diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html
index be267cc4d..fcc17c87d 100644
--- a/apps/assets/templates/assets/asset_list.html
+++ b/apps/assets/templates/assets/asset_list.html
@@ -426,13 +426,15 @@ $(document).ready(function(){
function success(data) {
url = setUrlParam(the_url, 'spm', data.spm);
requestApi({
- url:url,
- method:'DELETE',
- success:refreshPage,
- flash_message:false,
+ url: url,
+ method: 'DELETE',
+ success: function () {
+ 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() {
var msg = "{% trans 'Asset Deleting failed.' %}";
@@ -440,10 +442,11 @@ $(document).ready(function(){
}
requestApi({
url: "{% url 'api-common:resources-cache' %}",
- method:'POST',
- body:JSON.stringify(data),
- success:success,
- error:fail
+ method: 'POST',
+ body: JSON.stringify(data),
+ success: success,
+ error: fail,
+ flash_message: false
})
})
}
diff --git a/apps/assets/templates/assets/domain_gateway_list.html b/apps/assets/templates/assets/domain_gateway_list.html
index ade46acc9..79636a2bc 100644
--- a/apps/assets/templates/assets/domain_gateway_list.html
+++ b/apps/assets/templates/assets/domain_gateway_list.html
@@ -139,7 +139,7 @@ $(document).ready(function(){
method: "POST",
body: JSON.stringify({'port': parseInt(data.port)}),
success_message: "{% trans 'Can be connected' %}",
- fail_message: "{% trans 'The connection fails' %}"
+ {#fail_message: "{% trans 'The connection fails' %}"#}
})
});
diff --git a/apps/authentication/signals_handlers.py b/apps/authentication/signals_handlers.py
index c0b48c61d..b894e0651 100644
--- a/apps/authentication/signals_handlers.py
+++ b/apps/authentication/signals_handlers.py
@@ -47,7 +47,7 @@ def on_openid_login_success(sender, user=None, request=None, **kwargs):
@receiver(populate_user)
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.save()
diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py
index 58ca170c6..f7f35c215 100644
--- a/apps/jumpserver/conf.py
+++ b/apps/jumpserver/conf.py
@@ -395,6 +395,7 @@ defaults = {
'FLOWER_URL': "127.0.0.1:5555",
'DEFAULT_ORG_SHOW_ALL_USERS': True,
'PERIOD_TASK_ENABLED': True,
+ 'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False,
}
diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py
index f6b81e877..fe4f2fc84 100644
--- a/apps/jumpserver/settings.py
+++ b/apps/jumpserver/settings.py
@@ -516,7 +516,7 @@ CELERY_TASK_EAGER_PROPAGATES = True
CELERY_WORKER_REDIRECT_STDOUTS = True
CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO"
# CELERY_WORKER_HIJACK_ROOT_LOGGER = True
-CELERY_WORKER_MAX_TASKS_PER_CHILD = 40
+# CELERY_WORKER_MAX_TASKS_PER_CHILD = 40
CELERY_TASK_SOFT_TIME_LIMIT = 3600
# Cache use redis
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index a6510ca5a..233d13246 100644
Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index fdc37654f..95f544397 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/zh/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-10-25 10:52+0800\n"
+"POT-Creation-Date: 2019-11-13 16:38+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler
\n"
"Language-Team: Jumpserver team\n"
@@ -83,7 +83,7 @@ msgstr "运行参数"
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:16
-#: assets/templates/assets/system_user_list.html:51 audits/models.py:19
+#: assets/templates/assets/system_user_list.html:51 audits/models.py:20
#: audits/templates/audits/ftp_log_list.html:44
#: audits/templates/audits/ftp_log_list.html:74
#: perms/forms/asset_permission.py:84 perms/models/asset_permission.py:80
@@ -137,7 +137,7 @@ msgstr "资产"
#: perms/templates/perms/remote_app_permission_remote_app.html:53
#: perms/templates/perms/remote_app_permission_user.html:53
#: 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/replay_storage_create.html:44
#: settings/templates/settings/terminal_setting.html:83
@@ -197,7 +197,7 @@ msgstr "参数"
#: orgs/models.py:16 perms/models/base.py:54
#: perms/templates/perms/asset_permission_detail.html:98
#: perms/templates/perms/remote_app_permission_detail.html:90
-#: users/models/user.py:414 users/serializers/v1.py:143
+#: users/models/user.py:414 users/serializers/group.py:32
#: users/templates/users/user_detail.html:111
#: xpack/plugins/change_auth_plan/models.py:108
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
@@ -308,7 +308,7 @@ msgstr "远程应用"
#: settings/templates/settings/security_setting.html:73
#: settings/templates/settings/terminal_setting.html:71
#: terminal/templates/terminal/terminal_update.html:45
-#: users/templates/users/_user.html:50
+#: users/templates/users/_user.html:51
#: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_detail.html:178
#: users/templates/users/user_group_create_update.html:31
@@ -352,7 +352,7 @@ msgstr "重置"
#: terminal/templates/terminal/command_list.html:47
#: terminal/templates/terminal/session_list.html:52
#: terminal/templates/terminal/terminal_update.html:46
-#: users/templates/users/_user.html:51
+#: users/templates/users/_user.html:52
#: users/templates/users/forgot_password.html:42
#: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:57
@@ -410,7 +410,7 @@ msgstr "详情"
#: assets/templates/assets/label_list.html:39
#: assets/templates/assets/system_user_detail.html:26
#: assets/templates/assets/system_user_list.html:29
-#: assets/templates/assets/system_user_list.html:81 audits/models.py:33
+#: assets/templates/assets/system_user_list.html:81 audits/models.py:34
#: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:178
#: perms/templates/perms/remote_app_permission_detail.html:30
@@ -454,7 +454,7 @@ msgstr "更新"
#: assets/templates/assets/domain_list.html:55
#: assets/templates/assets/label_list.html:40
#: assets/templates/assets/system_user_detail.html:30
-#: assets/templates/assets/system_user_list.html:82 audits/models.py:34
+#: assets/templates/assets/system_user_list.html:82 audits/models.py:35
#: authentication/templates/authentication/_access_key_modal.html:65
#: ops/templates/ops/task_list.html:69
#: perms/templates/perms/asset_permission_detail.html:34
@@ -510,7 +510,7 @@ msgstr "创建远程应用"
#: assets/templates/assets/domain_gateway_list.html:73
#: assets/templates/assets/domain_list.html:29
#: assets/templates/assets/label_list.html:17
-#: assets/templates/assets/system_user_list.html:56 audits/models.py:38
+#: assets/templates/assets/system_user_list.html:56 audits/models.py:39
#: audits/templates/audits/operate_log_list.html:47
#: audits/templates/audits/operate_log_list.html:73
#: authentication/templates/authentication/_access_key_modal.html:34
@@ -634,7 +634,7 @@ msgid "Domain"
msgstr "网域"
#: 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/templates/assets/asset_create.html:42
#: perms/forms/asset_permission.py:87 perms/forms/asset_permission.py:94
@@ -679,9 +679,9 @@ msgstr "选择资产"
msgid "Content should not be contain: {}"
msgstr "内容不能包含: {}"
-#: assets/forms/domain.py:55
+#: assets/forms/domain.py:55 assets/models/domain.py:67
msgid "Password should not contain special characters"
-msgstr "不能包含特殊字符"
+msgstr "密码不能包含特殊字符"
#: assets/forms/domain.py:74
msgid "SSH gateway support proxy SSH,RDP,VNC"
@@ -696,14 +696,14 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: assets/templates/assets/admin_user_list.html:45
#: assets/templates/assets/domain_gateway_list.html:71
#: assets/templates/assets/system_user_detail.html:62
-#: assets/templates/assets/system_user_list.html:48 audits/models.py:80
+#: assets/templates/assets/system_user_list.html:48 audits/models.py:81
#: audits/templates/audits/login_log_list.html:57 authentication/forms.py:13
#: authentication/templates/authentication/login.html:65
#: authentication/templates/authentication/new_login.html:92
#: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70
#: perms/templates/perms/asset_permission_user.html:55
#: perms/templates/perms/remote_app_permission_user.html:54
-#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:13
+#: settings/templates/settings/_ldap_list_users_modal.html:31 users/forms.py:14
#: users/models/user.py:371 users/templates/users/_select_user_modal.html:14
#: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:36
@@ -730,7 +730,7 @@ msgstr "密码或密钥密码"
#: authentication/forms.py:15
#: authentication/templates/authentication/login.html:68
#: authentication/templates/authentication/new_login.html:95
-#: settings/forms.py:114 users/forms.py:15 users/forms.py:27
+#: settings/forms.py:114 users/forms.py:16 users/forms.py:42
#: users/templates/users/reset_password.html:53
#: users/templates/users/user_password_authentication.html:18
#: users/templates/users/user_password_update.html:44
@@ -1090,8 +1090,8 @@ msgstr "资产组"
msgid "Default asset group"
msgstr "默认资产组"
-#: assets/models/label.py:15 audits/models.py:17 audits/models.py:37
-#: audits/models.py:50 audits/templates/audits/ftp_log_list.html:36
+#: assets/models/label.py:15 audits/models.py:18 audits/models.py:38
+#: audits/models.py:51 audits/templates/audits/ftp_log_list.html:36
#: audits/templates/audits/ftp_log_list.html:73
#: audits/templates/audits/operate_log_list.html:39
#: audits/templates/audits/operate_log_list.html:72
@@ -1110,9 +1110,10 @@ msgstr "默认资产组"
#: terminal/models.py:156 terminal/templates/terminal/command_list.html:29
#: terminal/templates/terminal/command_list.html:65
#: terminal/templates/terminal/session_list.html:27
-#: terminal/templates/terminal/session_list.html:71 users/forms.py:319
+#: terminal/templates/terminal/session_list.html:71 users/forms.py:339
#: users/models/user.py:127 users/models/user.py:143 users/models/user.py:500
-#: users/serializers/v1.py:132 users/templates/users/user_group_detail.html:78
+#: users/serializers/group.py:21
+#: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:36 users/views/user.py:250
#: xpack/plugins/orgs/forms.py:28
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
@@ -1120,7 +1121,7 @@ msgstr "默认资产组"
msgid "User"
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
msgid "Value"
msgstr "值"
@@ -1129,23 +1130,23 @@ msgstr "值"
msgid "Category"
msgstr "分类"
-#: assets/models/node.py:163
+#: assets/models/node.py:164
msgid "New node"
msgstr "新节点"
-#: assets/models/node.py:324
+#: assets/models/node.py:325
msgid "ungrouped"
msgstr "未分组"
-#: assets/models/node.py:326
+#: assets/models/node.py:327
msgid "empty"
msgstr "空"
-#: assets/models/node.py:328
+#: assets/models/node.py:329
msgid "favorite"
msgstr "收藏夹"
-#: assets/models/node.py:411
+#: assets/models/node.py:452
msgid "Key"
msgstr "键"
@@ -1200,7 +1201,7 @@ msgid "Login mode"
msgstr "登录模式"
#: assets/models/user.py:166 assets/templates/assets/user_asset_list.html:79
-#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:52
+#: audits/models.py:21 audits/templates/audits/ftp_log_list.html:52
#: audits/templates/audits/ftp_log_list.html:75
#: perms/forms/asset_permission.py:90 perms/forms/remote_app_permission.py:43
#: perms/models/asset_permission.py:82 perms/models/remote_app_permission.py:16
@@ -1265,7 +1266,7 @@ msgstr "组织名称"
msgid "Backend"
msgstr "后端"
-#: assets/serializers/asset_user.py:67 users/forms.py:262
+#: assets/serializers/asset_user.py:67 users/forms.py:282
#: users/models/user.py:403 users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:49
#: users/templates/users/user_profile.html:69
@@ -1436,9 +1437,10 @@ msgid "Asset list"
msgstr "资产列表"
#: assets/templates/assets/_asset_list_modal.html:33
-#: assets/templates/assets/_node_tree.html:40
+#: assets/templates/assets/_node_tree.html:39
#: ops/templates/ops/command_execution_create.html:70
#: ops/templates/ops/command_execution_create.html:127
+#: settings/templates/settings/_ldap_list_users_modal.html:41
#: users/templates/users/_granted_assets.html:7
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66
msgid "Loading"
@@ -1481,7 +1483,7 @@ msgstr "获取认证信息错误"
#: assets/templates/assets/_user_asset_detail_modal.html:23
#: authentication/templates/authentication/_access_key_modal.html:142
#: 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
msgid "Close"
msgstr "关闭"
@@ -1531,31 +1533,31 @@ msgstr "SSH端口"
msgid "If use nat, set the ssh real port"
msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口"
-#: assets/templates/assets/_node_tree.html:50
+#: assets/templates/assets/_node_tree.html:49
msgid "Add node"
msgstr "新建节点"
-#: assets/templates/assets/_node_tree.html:51
+#: assets/templates/assets/_node_tree.html:50
msgid "Rename node"
msgstr "重命名节点"
-#: assets/templates/assets/_node_tree.html:52
+#: assets/templates/assets/_node_tree.html:51
msgid "Delete node"
msgstr "删除节点"
-#: assets/templates/assets/_node_tree.html:166
+#: assets/templates/assets/_node_tree.html:165
msgid "Create node failed"
msgstr "创建节点失败"
-#: assets/templates/assets/_node_tree.html:178
+#: assets/templates/assets/_node_tree.html:177
msgid "Have child node, cancel"
msgstr "存在子节点,不能删除"
-#: assets/templates/assets/_node_tree.html:180
+#: assets/templates/assets/_node_tree.html:179
msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
-#: assets/templates/assets/_node_tree.html:255
+#: assets/templates/assets/_node_tree.html:254
msgid "Rename success"
msgstr "重命名成功"
@@ -1697,7 +1699,7 @@ msgstr "导出"
#: assets/templates/assets/admin_user_list.html:21
#: assets/templates/assets/asset_list.html:73
#: 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_list.html:15
#: xpack/plugins/license/templates/license/license_detail.html:110
@@ -1882,16 +1884,16 @@ msgstr "删除选择资产"
msgid "Cancel"
msgstr "取消"
-#: assets/templates/assets/asset_list.html:434
+#: assets/templates/assets/asset_list.html:432
msgid "Asset Deleted."
msgstr "已被删除"
-#: assets/templates/assets/asset_list.html:435
-#: assets/templates/assets/asset_list.html:439
+#: assets/templates/assets/asset_list.html:433
+#: assets/templates/assets/asset_list.html:441
msgid "Asset Delete"
msgstr "删除"
-#: assets/templates/assets/asset_list.html:438
+#: assets/templates/assets/asset_list.html:440
msgid "Asset Deleting failed."
msgstr "删除失败"
@@ -1983,10 +1985,6 @@ msgstr "测试连接"
msgid "Can be connected"
msgstr "可连接"
-#: assets/templates/assets/domain_gateway_list.html:142
-msgid "The connection fails"
-msgstr "连接失败"
-
#: assets/templates/assets/domain_list.html:9
msgid ""
"The domain function is added to address the fact that some environments "
@@ -2180,7 +2178,7 @@ msgstr "资产管理"
msgid "System user asset"
msgstr "系统用户资产"
-#: audits/models.py:18 audits/models.py:41 audits/models.py:52
+#: audits/models.py:19 audits/models.py:42 audits/models.py:53
#: audits/templates/audits/ftp_log_list.html:76
#: audits/templates/audits/operate_log_list.html:76
#: audits/templates/audits/password_change_log_list.html:58
@@ -2190,16 +2188,16 @@ msgstr "系统用户资产"
msgid "Remote addr"
msgstr "远端地址"
-#: audits/models.py:21 audits/templates/audits/ftp_log_list.html:77
+#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:77
msgid "Operate"
msgstr "操作"
-#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:59
+#: audits/models.py:23 audits/templates/audits/ftp_log_list.html:59
#: audits/templates/audits/ftp_log_list.html:78
msgid "Filename"
msgstr "文件名"
-#: audits/models.py:23 audits/models.py:76
+#: audits/models.py:24 audits/models.py:77
#: audits/templates/audits/ftp_log_list.html:79
#: ops/templates/ops/command_execution_list.html:68
#: ops/templates/ops/task_list.html:15
@@ -2209,67 +2207,67 @@ msgstr "文件名"
msgid "Success"
msgstr "成功"
-#: audits/models.py:32
+#: audits/models.py:33
#: authentication/templates/authentication/_access_key_modal.html:22
#: xpack/plugins/vault/templates/vault/vault.html:46
msgid "Create"
msgstr "创建"
-#: audits/models.py:39 audits/templates/audits/operate_log_list.html:55
+#: audits/models.py:40 audits/templates/audits/operate_log_list.html:55
#: audits/templates/audits/operate_log_list.html:74
msgid "Resource Type"
msgstr "资源类型"
-#: audits/models.py:40 audits/templates/audits/operate_log_list.html:75
+#: audits/models.py:41 audits/templates/audits/operate_log_list.html:75
msgid "Resource"
msgstr "资源"
-#: audits/models.py:51 audits/templates/audits/password_change_log_list.html:57
+#: audits/models.py:52 audits/templates/audits/password_change_log_list.html:57
msgid "Change by"
msgstr "修改者"
-#: audits/models.py:70 users/templates/users/user_detail.html:98
+#: audits/models.py:71 users/templates/users/user_detail.html:98
msgid "Disabled"
msgstr "禁用"
-#: audits/models.py:71 settings/models.py:33
+#: audits/models.py:72 settings/models.py:33
#: users/templates/users/user_detail.html:96
msgid "Enabled"
msgstr "启用"
-#: audits/models.py:72
+#: audits/models.py:73
msgid "-"
msgstr ""
-#: audits/models.py:77 xpack/plugins/cloud/models.py:264
+#: audits/models.py:78 xpack/plugins/cloud/models.py:264
#: xpack/plugins/cloud/models.py:287
msgid "Failed"
msgstr "失败"
-#: audits/models.py:81
+#: audits/models.py:82
msgid "Login type"
msgstr "登录方式"
-#: audits/models.py:82
+#: audits/models.py:83
msgid "Login ip"
msgstr "登录IP"
-#: audits/models.py:83
+#: audits/models.py:84
msgid "Login city"
msgstr "登录城市"
-#: audits/models.py:84
+#: audits/models.py:85
msgid "User agent"
msgstr "Agent"
-#: audits/models.py:85 audits/templates/audits/login_log_list.html:62
+#: audits/models.py:86 audits/templates/audits/login_log_list.html:62
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
-#: users/forms.py:174 users/models/user.py:395
+#: users/forms.py:194 users/models/user.py:395
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
-#: audits/models.py:86 audits/templates/audits/login_log_list.html:63
+#: audits/models.py:87 audits/templates/audits/login_log_list.html:63
#: xpack/plugins/change_auth_plan/models.py:416
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
#: xpack/plugins/cloud/models.py:278
@@ -2277,14 +2275,14 @@ msgstr "MFA"
msgid "Reason"
msgstr "原因"
-#: audits/models.py:87 audits/templates/audits/login_log_list.html:64
+#: audits/models.py:88 audits/templates/audits/login_log_list.html:64
#: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65
msgid "Status"
msgstr "状态"
-#: audits/models.py:88
+#: audits/models.py:89
msgid "Date login"
msgstr "登录日期"
@@ -2487,7 +2485,7 @@ msgid ""
"after {} minutes)"
msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)"
-#: authentication/forms.py:66 users/forms.py:21
+#: authentication/forms.py:66 users/forms.py:22
msgid "MFA code"
msgstr "MFA 验证码"
@@ -2826,23 +2824,23 @@ msgstr "Become"
msgid "Create by"
msgstr "创建者"
-#: ops/models/adhoc.py:251
+#: ops/models/adhoc.py:252
msgid "{} Start task: {}"
msgstr "{} 任务开始: {}"
-#: ops/models/adhoc.py:263
+#: ops/models/adhoc.py:264
msgid "{} Task finish"
msgstr "{} 任务结束"
-#: ops/models/adhoc.py:355
+#: ops/models/adhoc.py:356
msgid "Start time"
msgstr "开始时间"
-#: ops/models/adhoc.py:356
+#: ops/models/adhoc.py:357
msgid "End time"
msgstr "完成时间"
-#: ops/models/adhoc.py:357 ops/templates/ops/adhoc_history.html:57
+#: ops/models/adhoc.py:358 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:17
#: xpack/plugins/change_auth_plan/models.py:252
#: xpack/plugins/change_auth_plan/models.py:422
@@ -2852,23 +2850,23 @@ msgstr "完成时间"
msgid "Time"
msgstr "时间"
-#: ops/models/adhoc.py:358 ops/templates/ops/adhoc_detail.html:106
+#: ops/models/adhoc.py:359 ops/templates/ops/adhoc_detail.html:106
#: ops/templates/ops/adhoc_history.html:55
#: ops/templates/ops/adhoc_history_detail.html:69
#: ops/templates/ops/task_detail.html:84 ops/templates/ops/task_history.html:61
msgid "Is finished"
msgstr "是否完成"
-#: ops/models/adhoc.py:359 ops/templates/ops/adhoc_history.html:56
+#: ops/models/adhoc.py:360 ops/templates/ops/adhoc_history.html:56
#: ops/templates/ops/task_history.html:62
msgid "Is success"
msgstr "是否成功"
-#: ops/models/adhoc.py:360
+#: ops/models/adhoc.py:361
msgid "Adhoc raw result"
msgstr "结果"
-#: ops/models/adhoc.py:361
+#: ops/models/adhoc.py:362
msgid "Adhoc result summary"
msgstr "汇总"
@@ -3135,7 +3133,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
#: perms/templates/perms/asset_permission_list.html:71
#: perms/templates/perms/asset_permission_list.html:118
#: perms/templates/perms/remote_app_permission_list.html:16
-#: templates/_nav.html:21 users/forms.py:293 users/models/group.py:26
+#: templates/_nav.html:21 users/forms.py:313 users/models/group.py:26
#: users/models/user.py:379 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:218
#: users/templates/users/user_list.html:38
@@ -3395,33 +3393,41 @@ msgstr "远程应用授权用户列表"
msgid "RemoteApp permission RemoteApp list"
msgstr "远程应用授权远程应用列表"
-#: settings/api.py:28
+#: settings/api.py:37
msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查"
-#: settings/api.py:67
+#: settings/api.py:76
msgid "Test ldap success"
msgstr "连接LDAP成功"
-#: settings/api.py:104
+#: settings/api.py:107
+msgid "LDAP attr map not valid"
+msgstr "LDAP 属性映射无效"
+
+#: settings/api.py:116
msgid "Match {} s users"
msgstr "匹配 {} 个用户"
-#: settings/api.py:163
-msgid "succeed: {} failed: {} total: {}"
-msgstr "成功:{} 失败:{} 总数:{}"
+#: settings/api.py:224
+msgid "Get ldap users is None"
+msgstr "获取 LDAP 用户为 None"
-#: settings/api.py:185 settings/api.py:221
+#: settings/api.py:231
+msgid "Imported {} users successfully"
+msgstr "导入 {} 个用户成功"
+
+#: settings/api.py:262 settings/api.py:298
msgid ""
"Error: Account invalid (Please make sure the information such as Access key "
"or Secret key is correct)"
msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)"
-#: settings/api.py:191 settings/api.py:227
+#: settings/api.py:268 settings/api.py:304
msgid "Create succeed"
msgstr "创建成功"
-#: settings/api.py:209 settings/api.py:247
+#: settings/api.py:286 settings/api.py:324
#: settings/templates/settings/terminal_setting.html:154
msgid "Delete succeed"
msgstr "删除成功"
@@ -3741,23 +3747,32 @@ msgstr "LDAP 用户列表"
msgid "Please submit the LDAP configuration before import"
msgstr "请先提交LDAP配置再进行导入"
-#: settings/templates/settings/_ldap_list_users_modal.html:32
+#: settings/templates/settings/_ldap_list_users_modal.html:26
+msgid "Refresh cache"
+msgstr "刷新缓存"
+
+#: settings/templates/settings/_ldap_list_users_modal.html:33
#: users/models/user.py:375 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59
msgid "Email"
msgstr "邮件"
-#: settings/templates/settings/_ldap_list_users_modal.html:33
+#: settings/templates/settings/_ldap_list_users_modal.html:34
msgid "Existing"
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/email_content_setting.html:15
#: settings/templates/settings/email_setting.html:15
#: settings/templates/settings/ldap_setting.html:15
#: settings/templates/settings/security_setting.html:15
#: 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"
msgstr "基本设置"
@@ -3766,7 +3781,7 @@ msgstr "基本设置"
#: settings/templates/settings/email_setting.html:18
#: settings/templates/settings/ldap_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"
msgstr "邮件设置"
@@ -3775,7 +3790,7 @@ msgstr "邮件设置"
#: settings/templates/settings/email_setting.html:21
#: settings/templates/settings/ldap_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"
msgstr "邮件内容设置"
@@ -3784,7 +3799,7 @@ msgstr "邮件内容设置"
#: settings/templates/settings/email_setting.html:24
#: settings/templates/settings/ldap_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"
msgstr "LDAP设置"
@@ -3793,7 +3808,7 @@ msgstr "LDAP设置"
#: settings/templates/settings/email_setting.html:27
#: settings/templates/settings/ldap_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"
msgstr "终端设置"
@@ -3803,7 +3818,7 @@ msgstr "终端设置"
#: settings/templates/settings/ldap_setting.html:30
#: settings/templates/settings/security_setting.html:30
#: 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"
msgstr "安全设置"
@@ -3823,15 +3838,10 @@ msgstr "文档类型"
msgid "Create User setting"
msgstr "创建用户设置"
-#: settings/templates/settings/ldap_setting.html:68
+#: settings/templates/settings/ldap_setting.html:66
msgid "Bulk import"
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
msgid "Bucket"
msgstr "桶名称"
@@ -3936,30 +3946,26 @@ msgstr "删除失败"
msgid "Are you sure about deleting it?"
msgstr "您确定删除吗?"
-#: settings/utils.py:98
+#: settings/utils/ldap.py:128
msgid "Search no entry matched in ou {}"
msgstr "在ou:{}中没有匹配条目"
-#: settings/utils.py:172
-msgid "The user source is not LDAP"
-msgstr "用户来源不是LDAP"
-
-#: settings/views.py:19 settings/views.py:46 settings/views.py:73
-#: settings/views.py:103 settings/views.py:131 settings/views.py:144
-#: settings/views.py:158 settings/views.py:185 templates/_nav.html:170
+#: settings/views.py:20 settings/views.py:47 settings/views.py:74
+#: settings/views.py:105 settings/views.py:133 settings/views.py:146
+#: settings/views.py:160 settings/views.py:187 templates/_nav.html:170
msgid "Settings"
msgstr "系统设置"
-#: settings/views.py:30 settings/views.py:57 settings/views.py:84
-#: settings/views.py:116 settings/views.py:169 settings/views.py:196
+#: settings/views.py:31 settings/views.py:58 settings/views.py:85
+#: settings/views.py:118 settings/views.py:171 settings/views.py:198
msgid "Update setting successfully"
msgstr "更新设置成功"
-#: settings/views.py:132
+#: settings/views.py:134
msgid "Create replay storage"
msgstr "创建录像存储"
-#: settings/views.py:145
+#: settings/views.py:147
msgid "Create command storage"
msgstr "创建命令存储"
@@ -3976,8 +3982,8 @@ msgid "Commercial support"
msgstr "商业支持"
#: templates/_header_bar.html:70 templates/_nav.html:30
-#: templates/_nav_user.html:32 users/forms.py:153
-#: users/templates/users/_user.html:43
+#: templates/_nav_user.html:32 users/forms.py:173
+#: users/templates/users/_user.html:44
#: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:40
#: users/templates/users/user_profile.html:17
@@ -4541,7 +4547,7 @@ msgstr "你可以使用ssh客户端工具连接终端"
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
-#: users/forms.py:32 users/models/user.py:383
+#: users/forms.py:47 users/models/user.py:383
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:37
@@ -4549,44 +4555,51 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
msgid "Role"
msgstr "角色"
-#: users/forms.py:35 users/forms.py:232
+#: users/forms.py:51 users/models/user.py:418
+#: users/templates/users/user_detail.html:103
+#: users/templates/users/user_list.html:39
+#: users/templates/users/user_profile.html:102
+msgid "Source"
+msgstr "用户来源"
+
+#: users/forms.py:54 users/forms.py:252
#: users/templates/users/user_update.html:30
msgid "ssh public key"
msgstr "ssh公钥"
-#: users/forms.py:36 users/forms.py:233
+#: users/forms.py:55 users/forms.py:253
msgid "ssh-rsa AAAA..."
msgstr ""
-#: users/forms.py:37
+#: users/forms.py:56
msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里"
-#: users/forms.py:51 users/templates/users/user_detail.html:226
+#: users/forms.py:71 users/templates/users/user_detail.html:226
msgid "Join user groups"
msgstr "添加到用户组"
-#: users/forms.py:86 users/forms.py:247
+#: users/forms.py:106 users/forms.py:267
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
-#: users/forms.py:90 users/forms.py:251 users/serializers/v1.py:116
+#: users/forms.py:110 users/forms.py:271 users/serializers/user.py:110
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
-#: users/forms.py:103 users/views/login.py:114 users/views/user.py:287
+#: users/forms.py:123 users/views/login.py:114 users/views/user.py:287
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
-#: users/forms.py:124
+#: users/forms.py:144
msgid "Reset link will be generated and sent to the user"
msgstr "生成重置密码链接,通过邮件发送给用户"
-#: users/forms.py:125
+#: users/forms.py:145
msgid "Set password"
msgstr "设置密码"
-#: users/forms.py:132 xpack/plugins/change_auth_plan/models.py:88
+#: users/forms.py:152 xpack/plugins/change_auth_plan/models.py:88
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57
@@ -4594,7 +4607,7 @@ msgstr "设置密码"
msgid "Password strategy"
msgstr "密码策略"
-#: users/forms.py:159
+#: users/forms.py:179
msgid ""
"When enabled, you will enter the MFA binding process the next time you log "
"in. you can also directly bind in \"personal information -> quick "
@@ -4603,11 +4616,11 @@ msgstr ""
"启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修改->更"
"改MFA设置)中直接绑定!"
-#: users/forms.py:169
+#: users/forms.py:189
msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全。"
-#: users/forms.py:179
+#: users/forms.py:199
msgid ""
"In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex "
@@ -4616,41 +4629,41 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)"
-#: users/forms.py:186 users/templates/users/first_login.html:48
+#: users/forms.py:206 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:110
#: users/templates/users/first_login.html:139
msgid "Finish"
msgstr "完成"
-#: users/forms.py:192
+#: users/forms.py:212
msgid "Old password"
msgstr "原来密码"
-#: users/forms.py:197
+#: users/forms.py:217
msgid "New password"
msgstr "新密码"
-#: users/forms.py:202
+#: users/forms.py:222
msgid "Confirm password"
msgstr "确认密码"
-#: users/forms.py:212
+#: users/forms.py:232
msgid "Old password error"
msgstr "原来密码错误"
-#: users/forms.py:220
+#: users/forms.py:240
msgid "Password does not match"
msgstr "密码不一致"
-#: users/forms.py:230
+#: users/forms.py:250
msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥"
-#: users/forms.py:234
+#: users/forms.py:254
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
-#: users/forms.py:268 users/forms.py:273 users/forms.py:323
+#: users/forms.py:288 users/forms.py:293 users/forms.py:343
#: xpack/plugins/orgs/forms.py:18
msgid "Select users"
msgstr "选择用户"
@@ -4693,12 +4706,6 @@ msgstr "头像"
msgid "Wechat"
msgstr "微信"
-#: users/models/user.py:418 users/templates/users/user_detail.html:103
-#: users/templates/users/user_list.html:39
-#: users/templates/users/user_profile.html:102
-msgid "Source"
-msgstr "用户来源"
-
#: users/models/user.py:422
msgid "Date password last updated"
msgstr "最后更新密码日期"
@@ -4707,46 +4714,46 @@ msgstr "最后更新密码日期"
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
-#: users/serializers/v1.py:45
+#: users/serializers/group.py:46
+msgid "Auditors cannot be join in the user group"
+msgstr "审计员不能被加入到用户组"
+
+#: users/serializers/user.py:40
msgid "Groups name"
msgstr "用户组名"
-#: users/serializers/v1.py:46
+#: users/serializers/user.py:41
msgid "Source name"
msgstr "用户来源名"
-#: users/serializers/v1.py:47
+#: users/serializers/user.py:42
msgid "Is first login"
msgstr "首次登录"
-#: users/serializers/v1.py:48
+#: users/serializers/user.py:43
msgid "Role name"
msgstr "角色名"
-#: users/serializers/v1.py:49
+#: users/serializers/user.py:44
msgid "Is valid"
msgstr "账户是否有效"
-#: users/serializers/v1.py:50
+#: users/serializers/user.py:45
msgid "Is expired"
msgstr " 是否过期"
-#: users/serializers/v1.py:51
+#: users/serializers/user.py:46
msgid "Avatar url"
msgstr "头像路径"
-#: users/serializers/v1.py:72
+#: users/serializers/user.py:66
msgid "Role limit to {}"
msgstr "角色只能为 {}"
-#: users/serializers/v1.py:84
+#: users/serializers/user.py:78
msgid "Password does not match security rules"
msgstr "密码不满足安全规则"
-#: users/serializers/v1.py:157
-msgid "Auditors cannot be join in the user group"
-msgstr "审计员不能被加入到用户组"
-
#: users/serializers_v2/user.py:36
msgid "name not unique"
msgstr "名称重复"
@@ -4779,7 +4786,7 @@ msgstr "选择用户"
msgid "Asset num"
msgstr "资产数量"
-#: users/templates/users/_user.html:26
+#: users/templates/users/_user.html:27
msgid "Security and Role"
msgstr "角色安全"
@@ -6206,6 +6213,14 @@ msgstr "密码匣子"
msgid "vault create"
msgstr "创建"
+#, fuzzy
+#~| msgid "Password should not contain special characters"
+#~ msgid "Password has special char"
+#~ msgstr "不能包含特殊字符"
+
+#~ msgid "The connection fails"
+#~ msgstr "连接失败"
+
#~ msgid "Recipient"
#~ msgstr "收件人"
@@ -6331,25 +6346,6 @@ msgstr "创建"
#~ msgid "Sync User"
#~ 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 ""
#~ "Import {} users successfully;import {} users failed, Because’TypeError' "
#~ "object has no attribute 'keys'"
diff --git a/apps/perms/signals_handler.py b/apps/perms/signals_handler.py
index eecd101e8..4a7fe389c 100644
--- a/apps/perms/signals_handler.py
+++ b/apps/perms/signals_handler.py
@@ -11,13 +11,6 @@ from .utils.asset_permission import AssetPermissionUtilV2
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)
@on_transaction_commit
diff --git a/apps/settings/api.py b/apps/settings/api.py
index 22a295b68..1fce9a44d 100644
--- a/apps/settings/api.py
+++ b/apps/settings/api.py
@@ -13,17 +13,26 @@ from django.core.mail import send_mail
from django.utils.translation import ugettext_lazy as _
from .models import Setting
-from .utils import LDAPUtil
+from .utils import (
+ LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil,
+ LDAP_USE_CACHE_FLAGS
+
+)
+from .tasks import sync_ldap_user_task
from common.permissions import IsOrgAdmin, IsSuperUser
from common.utils import get_logger
-from .serializers import MailTestSerializer, LDAPTestSerializer, LDAPUserSerializer
+from .serializers import (
+ MailTestSerializer, LDAPTestSerializer, LDAPUserSerializer,
+ PublicSettingSerializer,
+)
+from users.models import User
logger = get_logger(__file__)
class MailTestingAPI(APIView):
- permission_classes = (IsOrgAdmin,)
+ permission_classes = (IsSuperUser,)
serializer_class = MailTestSerializer
success_message = _("Test mail sent to {}, please check")
@@ -62,70 +71,83 @@ class MailTestingAPI(APIView):
class LDAPTestingAPI(APIView):
- permission_classes = (IsOrgAdmin,)
+ permission_classes = (IsSuperUser,)
serializer_class = LDAPTestSerializer
success_message = _("Test ldap success")
@staticmethod
- def get_ldap_util(serializer):
- host = serializer.validated_data["AUTH_LDAP_SERVER_URI"]
+ def get_ldap_config(serializer):
+ server_uri = serializer.validated_data["AUTH_LDAP_SERVER_URI"]
bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"]
password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"]
use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False)
search_ougroup = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
- try:
- attr_map = json.loads(attr_map)
- except json.JSONDecodeError:
- return Response({"error": "AUTH_LDAP_USER_ATTR_MAP not valid"}, status=401)
-
- util = LDAPUtil(
- use_settings_config=False, server_uri=host, bind_dn=bind_dn,
- password=password, use_ssl=use_ssl,
- search_ougroup=search_ougroup, search_filter=search_filter,
- attr_map=attr_map
- )
- return util
+ config = {
+ 'server_uri': server_uri,
+ 'bind_dn': bind_dn,
+ 'password': password,
+ 'use_ssl': use_ssl,
+ 'search_ougroup': search_ougroup,
+ 'search_filter': search_filter,
+ 'attr_map': json.loads(attr_map),
+ }
+ return config
def post(self, request):
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
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:
- 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:
return Response({"error": str(e)}, status=401)
- if len(users) > 0:
- return Response({"msg": _("Match {} s users").format(len(users))})
- else:
- return Response({"error": "Have user but attr mapping error"}, status=401)
+ return Response({"msg": _("Match {} s users").format(len(users))})
class LDAPUserListApi(generics.ListAPIView):
- permission_classes = (IsOrgAdmin,)
+ permission_classes = (IsSuperUser,)
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):
if hasattr(self, 'swagger_fake_view'):
return []
- q = self.request.query_params.get('search')
- try:
- util = LDAPUtil()
- extra_filter = util.construct_extra_filter(util.SEARCH_FIELD_ALL, q)
- users = util.search_user_items(extra_filter)
- except Exception as e:
- users = []
- logger.error(e)
- # 前端data_table会根据row.id对table.selected值进行操作
- for user in users:
- user['id'] = user['username']
+ cache_police = self.request.query_params.get('cache_police', True)
+ if cache_police in LDAP_USE_CACHE_FLAGS:
+ users = self.get_queryset_from_cache()
+ else:
+ users = self.get_queryset_from_server()
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):
order_by = self.request.query_params.get('order')
if not order_by:
@@ -138,32 +160,89 @@ class LDAPUserListApi(generics.ListAPIView):
queryset = sorted(queryset, key=lambda x: x[order_by], reverse=reverse)
return queryset
- def list(self, request, *args, **kwargs):
- queryset = self.get_queryset()
+ def filter_queryset(self, queryset):
+ if queryset is None:
+ return queryset
+ queryset = self.processing_queryset(queryset)
queryset = self.sort_queryset(queryset)
- page = self.paginate_queryset(queryset)
- if page is not None:
- return self.get_paginated_response(page)
- return Response(queryset)
+ return queryset
+
+ def list(self, request, *args, **kwargs):
+ cache_police = self.request.query_params.get('cache_police', True)
+ # 不是用缓存
+ if cache_police not in LDAP_USE_CACHE_FLAGS:
+ return super().list(request, *args, **kwargs)
+
+ try:
+ queryset = self.get_queryset()
+ except Exception as e:
+ data = {'error': str(e)}
+ return Response(data=data, status=400)
+
+ # 缓存有数据
+ if queryset is not None:
+ return super().list(request, *args, **kwargs)
+
+ sync_util = LDAPSyncUtil()
+ # 还没有同步任务
+ if sync_util.task_no_start:
+ # 任务外部设置 task running 状态
+ sync_util.set_task_status(sync_util.TASK_STATUS_IS_RUNNING)
+ task = sync_ldap_user_task.delay()
+ data = {'msg': 'Cache no data, sync task {} started.'.format(task.id)}
+ return Response(data=data, status=409)
+ # 同步任务正在执行
+ if sync_util.task_is_running:
+ data = {'msg': 'synchronization is running.'}
+ return Response(data=data, status=409)
+ # 同步任务执行结束
+ if sync_util.task_is_over:
+ msg = sync_util.get_task_error_msg()
+ data = {'error': 'Synchronization task report error: {}'.format(msg)}
+ return Response(data=data, status=400)
+
+ return super().list(request, *args, **kwargs)
-class LDAPUserSyncAPI(APIView):
- permission_classes = (IsOrgAdmin,)
+class LDAPUserImportAPI(APIView):
+ 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):
- username_list = request.data.get('username_list', [])
-
- util = LDAPUtil()
try:
- result = util.sync_users(username_list)
+ users = self.get_ldap_users()
except Exception as e:
- logger.error(e, exc_info=True)
return Response({'error': str(e)}, status=401)
- else:
- msg = _("succeed: {} failed: {} total: {}").format(
- result['succeed'], result['failed'], result['total']
- )
- return Response({'msg': msg})
+
+ if users is None:
+ return Response({'msg': _('Get ldap users is None')}, status=401)
+
+ 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):
@@ -245,3 +324,19 @@ class CommandStorageDeleteAPI(APIView):
storage_name = str(request.data.get('name'))
Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name)
return Response({"msg": _('Delete succeed')}, status=200)
+
+
+class PublicSettingApi(generics.RetrieveAPIView):
+ permission_classes = ()
+ serializer_class = PublicSettingSerializer
+
+ def get_object(self):
+ c = settings.CONFIG
+ instance = {
+ "data": {
+ "WINDOWS_SKIP_ALL_MANUAL_PASSWORD": c.WINDOWS_SKIP_ALL_MANUAL_PASSWORD
+ }
+ }
+ return instance
+
+
diff --git a/apps/settings/serializers.py b/apps/settings/serializers.py
index eb8a61679..1717634f4 100644
--- a/apps/settings/serializers.py
+++ b/apps/settings/serializers.py
@@ -25,6 +25,10 @@ class LDAPTestSerializer(serializers.Serializer):
class LDAPUserSerializer(serializers.Serializer):
id = serializers.CharField()
username = serializers.CharField()
+ name = serializers.CharField()
email = serializers.CharField()
existing = serializers.BooleanField(read_only=True)
+
+class PublicSettingSerializer(serializers.Serializer):
+ data = serializers.DictField(read_only=True)
diff --git a/apps/settings/tasks/__init__.py b/apps/settings/tasks/__init__.py
new file mode 100644
index 000000000..87bc6198f
--- /dev/null
+++ b/apps/settings/tasks/__init__.py
@@ -0,0 +1,4 @@
+# coding: utf-8
+#
+
+from .ldap import *
diff --git a/apps/settings/tasks/ldap.py b/apps/settings/tasks/ldap.py
new file mode 100644
index 000000000..60058e03e
--- /dev/null
+++ b/apps/settings/tasks/ldap.py
@@ -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()
diff --git a/apps/settings/templates/settings/_ldap_list_users_modal.html b/apps/settings/templates/settings/_ldap_list_users_modal.html
index dd839eb5b..8589b0066 100644
--- a/apps/settings/templates/settings/_ldap_list_users_modal.html
+++ b/apps/settings/templates/settings/_ldap_list_users_modal.html
@@ -23,6 +23,7 @@
@@ -43,8 +47,11 @@
diff --git a/apps/settings/templates/settings/ldap_setting.html b/apps/settings/templates/settings/ldap_setting.html
index 694fb66f9..5da66405d 100644
--- a/apps/settings/templates/settings/ldap_setting.html
+++ b/apps/settings/templates/settings/ldap_setting.html
@@ -63,9 +63,8 @@
-{# #}
-
+
@@ -109,33 +108,6 @@ $(document).ready(function () {
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
- });
- })
{% endblock %}
diff --git a/apps/settings/urls/api_urls.py b/apps/settings/urls/api_urls.py
index bc2e4731f..544de3941 100644
--- a/apps/settings/urls/api_urls.py
+++ b/apps/settings/urls/api_urls.py
@@ -10,9 +10,11 @@ urlpatterns = [
path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-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/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/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/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'),
+ path('public/', api.PublicSettingApi.as_view(), name='public-setting'),
]
diff --git a/apps/settings/utils.py b/apps/settings/utils.py
deleted file mode 100644
index 9ecd5d286..000000000
--- a/apps/settings/utils.py
+++ /dev/null
@@ -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
diff --git a/apps/settings/utils/__init__.py b/apps/settings/utils/__init__.py
new file mode 100644
index 000000000..87bc6198f
--- /dev/null
+++ b/apps/settings/utils/__init__.py
@@ -0,0 +1,4 @@
+# coding: utf-8
+#
+
+from .ldap import *
diff --git a/apps/settings/utils/ldap.py b/apps/settings/utils/ldap.py
new file mode 100644
index 000000000..e5ad1c3e8
--- /dev/null
+++ b/apps/settings/utils/ldap.py
@@ -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
+
+
+
diff --git a/apps/settings/views.py b/apps/settings/views.py
index a9df717d7..2442f074e 100644
--- a/apps/settings/views.py
+++ b/apps/settings/views.py
@@ -5,6 +5,7 @@ from django.utils.translation import ugettext as _
from common.permissions import PermissionsMixin, IsSuperUser
from common import utils
+from .utils import LDAPSyncUtil
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm
@@ -83,6 +84,7 @@ class LDAPSettingView(PermissionsMixin, TemplateView):
form.save()
msg = _("Update setting successfully")
messages.success(request, msg)
+ LDAPSyncUtil().clear_cache()
return redirect('settings:ldap-setting')
else:
context = self.get_context_data()
diff --git a/apps/users/forms.py b/apps/users/forms.py
index 98d7c9e09..649f66ab9 100644
--- a/apps/users/forms.py
+++ b/apps/users/forms.py
@@ -2,6 +2,7 @@
from django import forms
from django.utils.translation import gettext_lazy as _
+from django.conf import settings
from common.utils import validate_ssh_public_key
from orgs.mixins.forms import OrgModelForm
@@ -21,6 +22,20 @@ class UserCheckOtpCodeForm(forms.Form):
otp_code = forms.CharField(label=_('MFA code'), max_length=6)
+def get_source_choices():
+ choices_all = dict(User.SOURCE_CHOICES)
+ choices = [
+ (User.SOURCE_LOCAL, choices_all[User.SOURCE_LOCAL]),
+ ]
+ if settings.AUTH_LDAP:
+ choices.append((User.SOURCE_LDAP, choices_all[User.SOURCE_LDAP]))
+ if settings.AUTH_OPENID:
+ choices.append((User.SOURCE_OPENID, choices_all[User.SOURCE_OPENID]))
+ if settings.AUTH_RADIUS:
+ choices.append((User.SOURCE_RADIUS, choices_all[User.SOURCE_RADIUS]))
+ return choices
+
+
class UserCreateUpdateFormMixin(OrgModelForm):
role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP)
password = forms.CharField(
@@ -31,6 +46,10 @@ class UserCreateUpdateFormMixin(OrgModelForm):
choices=role_choices, required=True,
initial=User.ROLE_USER, label=_("Role")
)
+ source = forms.ChoiceField(
+ choices=get_source_choices, required=True,
+ initial=User.SOURCE_LOCAL, label=_("Source")
+ )
public_key = forms.CharField(
label=_('ssh public key'), max_length=5000, required=False,
widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}),
@@ -41,7 +60,8 @@ class UserCreateUpdateFormMixin(OrgModelForm):
model = User
fields = [
'username', 'name', 'email', 'groups', 'wechat',
- 'phone', 'role', 'date_expired', 'comment', 'otp_level'
+ 'source', 'phone', 'role', 'date_expired',
+ 'comment', 'otp_level'
]
widgets = {
'otp_level': forms.RadioSelect(),
diff --git a/apps/users/serializers/__init__.py b/apps/users/serializers/__init__.py
index 94ef71f28..78a695e51 100644
--- a/apps/users/serializers/__init__.py
+++ b/apps/users/serializers/__init__.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
#
-from .v1 import *
\ No newline at end of file
+from .user import *
+from .group import *
diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py
new file mode 100644
index 000000000..d27ddc19a
--- /dev/null
+++ b/apps/users/serializers/group.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+#
+from django.utils.translation import ugettext_lazy as _
+from rest_framework import serializers
+
+from common.fields import StringManyToManyField
+from common.serializers import AdaptedBulkListSerializer
+from orgs.mixins.serializers import BulkOrgResourceModelSerializer
+from ..models import User, UserGroup
+from .. import utils
+
+
+__all__ = [
+ 'UserGroupSerializer', 'UserGroupListSerializer',
+ 'UserGroupUpdateMemberSerializer'
+]
+
+
+class UserGroupSerializer(BulkOrgResourceModelSerializer):
+ users = serializers.PrimaryKeyRelatedField(
+ required=False, many=True, queryset=User.objects, label=_('User')
+ )
+
+ class Meta:
+ model = UserGroup
+ list_serializer_class = AdaptedBulkListSerializer
+ fields = [
+ 'id', 'name', 'users', 'comment', 'date_created',
+ 'created_by',
+ ]
+ extra_kwargs = {
+ 'created_by': {'label': _('Created by'), 'read_only': True}
+ }
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.set_fields_queryset()
+
+ def set_fields_queryset(self):
+ users_field = self.fields['users']
+ users_field.child_relation.queryset = utils.get_current_org_members()
+
+ def validate_users(self, users):
+ for user in users:
+ if user.is_super_auditor:
+ msg = _('Auditors cannot be join in the user group')
+ raise serializers.ValidationError(msg)
+ return users
+
+
+class UserGroupListSerializer(UserGroupSerializer):
+ users = StringManyToManyField(many=True, read_only=True)
+
+
+class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
+ users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects)
+
+ class Meta:
+ model = UserGroup
+ fields = ['id', 'users']
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.set_fields_queryset()
+
+ def set_fields_queryset(self):
+ users_field = self.fields['users']
+ users_field.child_relation.queryset = utils.get_current_org_members()
+
diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/user.py
similarity index 69%
rename from apps/users/serializers/v1.py
rename to apps/users/serializers/user.py
index 847afe885..57e2f43fa 100644
--- a/apps/users/serializers/v1.py
+++ b/apps/users/serializers/user.py
@@ -6,19 +6,14 @@ from rest_framework import serializers
from common.utils import validate_ssh_public_key
from common.mixins import BulkSerializerMixin
-from common.fields import StringManyToManyField
from common.serializers import AdaptedBulkListSerializer
from common.permissions import CanUpdateDeleteUser
-from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import User, UserGroup
-from .. import utils
__all__ = [
'UserSerializer', 'UserPKUpdateSerializer', 'UserUpdateGroupSerializer',
- 'UserGroupSerializer', 'UserGroupListSerializer',
- 'UserGroupUpdateMemberSerializer', 'ChangeUserPasswordSerializer',
- 'ResetOTPSerializer',
+ 'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
]
@@ -49,7 +44,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
'is_valid': {'label': _('Is valid')},
'is_expired': {'label': _('Is expired')},
'avatar_url': {'label': _('Avatar url')},
- 'source': {'read_only': True},
'created_by': {'read_only': True, 'allow_blank': True},
'can_update': {'read_only': True},
'can_delete': {'read_only': True},
@@ -127,58 +121,6 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer):
fields = ['id', 'groups']
-class UserGroupSerializer(BulkOrgResourceModelSerializer):
- users = serializers.PrimaryKeyRelatedField(
- required=False, many=True, queryset=User.objects, label=_('User')
- )
-
- class Meta:
- model = UserGroup
- list_serializer_class = AdaptedBulkListSerializer
- fields = [
- 'id', 'name', 'users', 'comment', 'date_created',
- 'created_by',
- ]
- extra_kwargs = {
- 'created_by': {'label': _('Created by'), 'read_only': True}
- }
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.set_fields_queryset()
-
- def set_fields_queryset(self):
- users_field = self.fields['users']
- users_field.child_relation.queryset = utils.get_current_org_members()
-
- def validate_users(self, users):
- for user in users:
- if user.is_super_auditor:
- msg = _('Auditors cannot be join in the user group')
- raise serializers.ValidationError(msg)
- return users
-
-
-class UserGroupListSerializer(UserGroupSerializer):
- users = StringManyToManyField(many=True, read_only=True)
-
-
-class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
- users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects)
-
- class Meta:
- model = UserGroup
- fields = ['id', 'users']
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.set_fields_queryset()
-
- def set_fields_queryset(self):
- users_field = self.fields['users']
- users_field.child_relation.queryset = utils.get_current_org_members()
-
-
class ChangeUserPasswordSerializer(serializers.ModelSerializer):
class Meta:
diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py
index 4c6afc663..a33d9eec9 100644
--- a/apps/users/signals_handler.py
+++ b/apps/users/signals_handler.py
@@ -2,11 +2,11 @@
#
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 .signals import post_user_create
-# from .models import User
+from .models import User
logger = get_logger(__file__)
@@ -28,3 +28,14 @@ def on_user_create(sender, user=None, **kwargs):
logger.info(" - Sending welcome mail ...".format(user.name))
if user.email:
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()
diff --git a/apps/users/tasks.py b/apps/users/tasks.py
index e0051e939..45e1c40cd 100644
--- a/apps/users/tasks.py
+++ b/apps/users/tasks.py
@@ -1,17 +1,20 @@
# -*- coding: utf-8 -*-
#
+import sys
from celery import shared_task
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 common.utils import get_logger
from .models import User
from .utils import (
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__)
@@ -70,19 +73,26 @@ def check_user_expired_periodic():
@shared_task
-def sync_ldap_user():
- logger.info("Start sync ldap user periodic task")
- util = LDAPUtil()
- result = util.sync_users()
- logger.info("Result: {}".format(result))
+def import_ldap_user():
+ logger.info("Start import ldap user task")
+ util_server = LDAPServerUtil()
+ util_import = LDAPImportUtil()
+ 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
@after_app_ready_start
-def sync_ldap_user_periodic():
+def import_ldap_user_periodic():
if not settings.AUTH_LDAP:
return
if not settings.AUTH_LDAP_SYNC_IS_PERIODIC:
+ task_name = sys._getframe().f_code.co_name
+ disable_celery_periodic_task(task_name)
return
interval = settings.AUTH_LDAP_SYNC_INTERVAL
@@ -91,10 +101,9 @@ def sync_ldap_user_periodic():
else:
interval = None
crontab = settings.AUTH_LDAP_SYNC_CRONTAB
-
tasks = {
- 'sync_ldap_user_periodic': {
- 'task': sync_ldap_user.name,
+ 'import_ldap_user_periodic': {
+ 'task': import_ldap_user.name,
'interval': interval,
'crontab': crontab,
'enabled': True,
diff --git a/apps/users/templates/users/_user.html b/apps/users/templates/users/_user.html
index 192dbfb70..ea0f76854 100644
--- a/apps/users/templates/users/_user.html
+++ b/apps/users/templates/users/_user.html
@@ -21,6 +21,7 @@
{% trans 'Auth' %}
{% block password %}{% endblock %}
{% bootstrap_field form.otp_level layout="horizontal" %}
+ {% bootstrap_field form.source layout="horizontal" %}
{% trans 'Security and Role' %}