mirror of https://github.com/jumpserver/jumpserver
Merge remote-tracking branch 'github/dev' into dev
commit
95a8bf0988
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: 2018-04-13 17:27+0800\n"
|
"POT-Creation-Date: 2018-04-18 20:14+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"
|
||||||
|
@ -154,7 +154,7 @@ msgstr "名称"
|
||||||
#: assets/templates/assets/system_user_detail.html:62
|
#: assets/templates/assets/system_user_detail.html:62
|
||||||
#: assets/templates/assets/system_user_list.html:27
|
#: assets/templates/assets/system_user_list.html:27
|
||||||
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
|
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
|
||||||
#: users/models/authentication.py:45 users/models/user.py:39
|
#: users/forms.py:22 users/models/authentication.py:45 users/models/user.py:39
|
||||||
#: users/templates/users/_select_user_modal.html:14
|
#: users/templates/users/_select_user_modal.html:14
|
||||||
#: users/templates/users/login.html:56
|
#: users/templates/users/login.html:56
|
||||||
#: users/templates/users/login_log_list.html:49
|
#: users/templates/users/login_log_list.html:49
|
||||||
|
@ -169,9 +169,11 @@ msgid "Password or private key passphrase"
|
||||||
msgstr "密码或密钥密码"
|
msgstr "密码或密钥密码"
|
||||||
|
|
||||||
#: assets/forms/user.py:25 assets/models/base.py:22 common/forms.py:113
|
#: assets/forms/user.py:25 assets/models/base.py:22 common/forms.py:113
|
||||||
#: users/forms.py:15 users/forms.py:24 users/templates/users/login.html:59
|
#: users/forms.py:15 users/forms.py:24 users/forms.py:36
|
||||||
|
#: users/templates/users/login.html:59
|
||||||
#: users/templates/users/reset_password.html:52
|
#: users/templates/users/reset_password.html:52
|
||||||
#: users/templates/users/user_create.html:11
|
#: users/templates/users/user_create.html:11
|
||||||
|
#: users/templates/users/user_password_authentication.html:13
|
||||||
#: users/templates/users/user_password_update.html:40
|
#: users/templates/users/user_password_update.html:40
|
||||||
#: users/templates/users/user_profile_update.html:40
|
#: users/templates/users/user_profile_update.html:40
|
||||||
#: users/templates/users/user_pubkey_update.html:40
|
#: users/templates/users/user_pubkey_update.html:40
|
||||||
|
@ -310,7 +312,7 @@ msgstr "标签管理"
|
||||||
#: assets/templates/assets/system_user_detail.html:96
|
#: assets/templates/assets/system_user_detail.html:96
|
||||||
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:28 perms/models.py:72
|
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:28 perms/models.py:72
|
||||||
#: perms/templates/perms/asset_permission_detail.html:98
|
#: perms/templates/perms/asset_permission_detail.html:98
|
||||||
#: users/models/user.py:55 users/templates/users/user_detail.html:99
|
#: users/models/user.py:55 users/templates/users/user_detail.html:107
|
||||||
msgid "Created by"
|
msgid "Created by"
|
||||||
msgstr "创建者"
|
msgstr "创建者"
|
||||||
|
|
||||||
|
@ -341,10 +343,10 @@ msgstr "创建日期"
|
||||||
#: ops/models/adhoc.py:42 perms/models.py:30 perms/models.py:74
|
#: ops/models/adhoc.py:42 perms/models.py:30 perms/models.py:74
|
||||||
#: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26
|
#: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26
|
||||||
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
|
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
|
||||||
#: users/models/user.py:52 users/templates/users/user_detail.html:111
|
#: users/models/user.py:52 users/templates/users/user_detail.html:119
|
||||||
#: users/templates/users/user_group_detail.html:67
|
#: users/templates/users/user_group_detail.html:67
|
||||||
#: users/templates/users/user_group_list.html:14
|
#: users/templates/users/user_group_list.html:14
|
||||||
#: users/templates/users/user_profile.html:114
|
#: users/templates/users/user_profile.html:122
|
||||||
msgid "Comment"
|
msgid "Comment"
|
||||||
msgstr "备注"
|
msgstr "备注"
|
||||||
|
|
||||||
|
@ -390,7 +392,7 @@ msgid "Default"
|
||||||
msgstr "默认"
|
msgstr "默认"
|
||||||
|
|
||||||
#: assets/models/cluster.py:36 assets/models/label.py:13
|
#: assets/models/cluster.py:36 assets/models/label.py:13
|
||||||
#: users/models/user.py:285
|
#: users/models/user.py:299
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "系统"
|
msgstr "系统"
|
||||||
|
|
||||||
|
@ -428,10 +430,10 @@ msgstr "默认资产组"
|
||||||
#: terminal/templates/terminal/command_list.html:32
|
#: terminal/templates/terminal/command_list.html:32
|
||||||
#: terminal/templates/terminal/command_list.html:72
|
#: terminal/templates/terminal/command_list.html:72
|
||||||
#: terminal/templates/terminal/session_list.html:33
|
#: terminal/templates/terminal/session_list.html:33
|
||||||
#: terminal/templates/terminal/session_list.html:71 users/forms.py:219
|
#: terminal/templates/terminal/session_list.html:71 users/forms.py:231
|
||||||
#: users/models/user.py:30 users/models/user.py:273
|
#: users/models/user.py:30 users/models/user.py:287
|
||||||
#: users/templates/users/user_group_detail.html:78
|
#: users/templates/users/user_group_detail.html:78
|
||||||
#: users/templates/users/user_group_list.html:13 users/views/user.py:335
|
#: users/templates/users/user_group_list.html:13 users/views/user.py:339
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "用户"
|
msgstr "用户"
|
||||||
|
|
||||||
|
@ -536,34 +538,6 @@ msgstr ""
|
||||||
msgid "推送系统用户到入资产: {}"
|
msgid "推送系统用户到入资产: {}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/templates/assets/_admin_user_setting_modal.html:4
|
|
||||||
#: users/templates/users/reset_password.html:57
|
|
||||||
#: users/templates/users/user_profile.html:20
|
|
||||||
msgid "Setting"
|
|
||||||
msgstr "设置"
|
|
||||||
|
|
||||||
#: assets/templates/assets/_admin_user_setting_modal.html:9
|
|
||||||
#: assets/templates/assets/_asset_import_modal.html:9
|
|
||||||
#: users/templates/users/_user_import_modal.html:10
|
|
||||||
msgid "Template"
|
|
||||||
msgstr "模板"
|
|
||||||
|
|
||||||
#: assets/templates/assets/_admin_user_setting_modal.html:10
|
|
||||||
#: assets/templates/assets/_asset_import_modal.html:10
|
|
||||||
#: users/templates/users/_user_import_modal.html:11
|
|
||||||
msgid "Download"
|
|
||||||
msgstr "下载"
|
|
||||||
|
|
||||||
#: assets/templates/assets/_admin_user_setting_modal.html:13
|
|
||||||
#: assets/templates/assets/_asset_import_modal.html:13
|
|
||||||
msgid "Asset csv file"
|
|
||||||
msgstr "资产csv文件"
|
|
||||||
|
|
||||||
#: assets/templates/assets/_admin_user_setting_modal.html:16
|
|
||||||
#: assets/templates/assets/_asset_import_modal.html:16
|
|
||||||
msgid "If set id, will use this id update asset existed"
|
|
||||||
msgstr "如果设置了id,则会使用该行信息更新该id的资产"
|
|
||||||
|
|
||||||
#: assets/templates/assets/_asset_group_bulk_update_modal.html:5
|
#: assets/templates/assets/_asset_group_bulk_update_modal.html:5
|
||||||
msgid "Update asset group"
|
msgid "Update asset group"
|
||||||
msgstr "更新用户组"
|
msgstr "更新用户组"
|
||||||
|
@ -594,6 +568,24 @@ msgstr "二次验证"
|
||||||
msgid "Import asset"
|
msgid "Import asset"
|
||||||
msgstr "导入资产"
|
msgstr "导入资产"
|
||||||
|
|
||||||
|
#: assets/templates/assets/_asset_import_modal.html:9
|
||||||
|
#: users/templates/users/_user_import_modal.html:10
|
||||||
|
msgid "Template"
|
||||||
|
msgstr "模板"
|
||||||
|
|
||||||
|
#: assets/templates/assets/_asset_import_modal.html:10
|
||||||
|
#: users/templates/users/_user_import_modal.html:11
|
||||||
|
msgid "Download"
|
||||||
|
msgstr "下载"
|
||||||
|
|
||||||
|
#: assets/templates/assets/_asset_import_modal.html:13
|
||||||
|
msgid "Asset csv file"
|
||||||
|
msgstr "资产csv文件"
|
||||||
|
|
||||||
|
#: assets/templates/assets/_asset_import_modal.html:16
|
||||||
|
msgid "If set id, will use this id update asset existed"
|
||||||
|
msgstr "如果设置了id,则会使用该行信息更新该id的资产"
|
||||||
|
|
||||||
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:50
|
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:50
|
||||||
#: templates/_nav.html:23
|
#: templates/_nav.html:23
|
||||||
msgid "Asset list"
|
msgid "Asset list"
|
||||||
|
@ -637,7 +629,7 @@ msgstr "其它"
|
||||||
#: assets/templates/assets/asset_update.html:70
|
#: assets/templates/assets/asset_update.html:70
|
||||||
#: assets/templates/assets/domain_create_update.html:16
|
#: assets/templates/assets/domain_create_update.html:16
|
||||||
#: assets/templates/assets/gateway_create_update.html:58
|
#: assets/templates/assets/gateway_create_update.html:58
|
||||||
#: assets/templates/assets/label_create_update.html:16
|
#: assets/templates/assets/label_create_update.html:18
|
||||||
#: common/templates/common/basic_setting.html:58
|
#: common/templates/common/basic_setting.html:58
|
||||||
#: common/templates/common/email_setting.html:59
|
#: common/templates/common/email_setting.html:59
|
||||||
#: common/templates/common/ldap_setting.html:59
|
#: common/templates/common/ldap_setting.html:59
|
||||||
|
@ -647,7 +639,7 @@ msgstr "其它"
|
||||||
#: users/templates/users/_user.html:43
|
#: users/templates/users/_user.html:43
|
||||||
#: users/templates/users/user_bulk_update.html:23
|
#: users/templates/users/user_bulk_update.html:23
|
||||||
#: users/templates/users/user_password_update.html:58
|
#: users/templates/users/user_password_update.html:58
|
||||||
#: users/templates/users/user_profile.html:151
|
#: users/templates/users/user_profile.html:180
|
||||||
#: users/templates/users/user_profile_update.html:63
|
#: users/templates/users/user_profile_update.html:63
|
||||||
#: users/templates/users/user_pubkey_update.html:70
|
#: users/templates/users/user_pubkey_update.html:70
|
||||||
#: users/templates/users/user_pubkey_update.html:76
|
#: users/templates/users/user_pubkey_update.html:76
|
||||||
|
@ -662,7 +654,7 @@ msgstr "重置"
|
||||||
#: assets/templates/assets/asset_update.html:71
|
#: assets/templates/assets/asset_update.html:71
|
||||||
#: assets/templates/assets/domain_create_update.html:17
|
#: assets/templates/assets/domain_create_update.html:17
|
||||||
#: assets/templates/assets/gateway_create_update.html:59
|
#: assets/templates/assets/gateway_create_update.html:59
|
||||||
#: assets/templates/assets/label_create_update.html:17
|
#: assets/templates/assets/label_create_update.html:19
|
||||||
#: common/templates/common/basic_setting.html:59
|
#: common/templates/common/basic_setting.html:59
|
||||||
#: common/templates/common/email_setting.html:60
|
#: common/templates/common/email_setting.html:60
|
||||||
#: common/templates/common/ldap_setting.html:60
|
#: common/templates/common/ldap_setting.html:60
|
||||||
|
@ -740,7 +732,7 @@ msgstr "测试"
|
||||||
#: assets/templates/assets/asset_list.html:170
|
#: assets/templates/assets/asset_list.html:170
|
||||||
#: assets/templates/assets/domain_detail.html:24
|
#: assets/templates/assets/domain_detail.html:24
|
||||||
#: assets/templates/assets/domain_detail.html:103
|
#: assets/templates/assets/domain_detail.html:103
|
||||||
#: assets/templates/assets/domain_gateway_list.html:90
|
#: assets/templates/assets/domain_gateway_list.html:85
|
||||||
#: assets/templates/assets/domain_list.html:42
|
#: assets/templates/assets/domain_list.html:42
|
||||||
#: assets/templates/assets/label_list.html:38
|
#: assets/templates/assets/label_list.html:38
|
||||||
#: assets/templates/assets/system_user_detail.html:26
|
#: assets/templates/assets/system_user_detail.html:26
|
||||||
|
@ -753,8 +745,8 @@ msgstr "测试"
|
||||||
#: users/templates/users/user_group_detail.html:28
|
#: users/templates/users/user_group_detail.html:28
|
||||||
#: users/templates/users/user_group_list.html:43
|
#: users/templates/users/user_group_list.html:43
|
||||||
#: users/templates/users/user_list.html:76
|
#: users/templates/users/user_list.html:76
|
||||||
#: users/templates/users/user_profile.html:135
|
|
||||||
#: users/templates/users/user_profile.html:143
|
#: users/templates/users/user_profile.html:143
|
||||||
|
#: users/templates/users/user_profile.html:172
|
||||||
msgid "Update"
|
msgid "Update"
|
||||||
msgstr "更新"
|
msgstr "更新"
|
||||||
|
|
||||||
|
@ -764,7 +756,7 @@ msgstr "更新"
|
||||||
#: assets/templates/assets/asset_list.html:171
|
#: assets/templates/assets/asset_list.html:171
|
||||||
#: assets/templates/assets/domain_detail.html:28
|
#: assets/templates/assets/domain_detail.html:28
|
||||||
#: assets/templates/assets/domain_detail.html:104
|
#: assets/templates/assets/domain_detail.html:104
|
||||||
#: assets/templates/assets/domain_gateway_list.html:91
|
#: assets/templates/assets/domain_gateway_list.html:86
|
||||||
#: assets/templates/assets/domain_list.html:43
|
#: assets/templates/assets/domain_list.html:43
|
||||||
#: assets/templates/assets/label_list.html:39
|
#: assets/templates/assets/label_list.html:39
|
||||||
#: assets/templates/assets/system_user_detail.html:30
|
#: assets/templates/assets/system_user_detail.html:30
|
||||||
|
@ -796,13 +788,13 @@ msgstr "选择节点"
|
||||||
#: assets/templates/assets/system_user_detail.html:183
|
#: assets/templates/assets/system_user_detail.html:183
|
||||||
#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22
|
#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22
|
||||||
#: terminal/templates/terminal/session_detail.html:108
|
#: terminal/templates/terminal/session_detail.html:108
|
||||||
#: users/templates/users/user_detail.html:339
|
#: users/templates/users/user_detail.html:357
|
||||||
#: users/templates/users/user_detail.html:364
|
#: users/templates/users/user_detail.html:382
|
||||||
#: users/templates/users/user_detail.html:387
|
#: users/templates/users/user_detail.html:405
|
||||||
#: users/templates/users/user_group_create_update.html:32
|
#: users/templates/users/user_group_create_update.html:32
|
||||||
#: users/templates/users/user_group_list.html:86
|
#: users/templates/users/user_group_list.html:86
|
||||||
#: users/templates/users/user_list.html:196
|
#: users/templates/users/user_list.html:196
|
||||||
#: users/templates/users/user_profile.html:185
|
#: users/templates/users/user_profile.html:214
|
||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "确认"
|
msgstr "确认"
|
||||||
|
|
||||||
|
@ -852,15 +844,15 @@ msgid "Disk"
|
||||||
msgstr "硬盘"
|
msgstr "硬盘"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_detail.html:121
|
#: assets/templates/assets/asset_detail.html:121
|
||||||
#: users/templates/users/user_detail.html:103
|
#: users/templates/users/user_detail.html:111
|
||||||
#: users/templates/users/user_profile.html:88
|
#: users/templates/users/user_profile.html:96
|
||||||
msgid "Date joined"
|
msgid "Date joined"
|
||||||
msgstr "创建日期"
|
msgstr "创建日期"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_detail.html:137
|
#: assets/templates/assets/asset_detail.html:137
|
||||||
#: terminal/templates/terminal/session_detail.html:81
|
#: terminal/templates/terminal/session_detail.html:81
|
||||||
#: users/templates/users/user_detail.html:122
|
#: users/templates/users/user_detail.html:130
|
||||||
#: users/templates/users/user_profile.html:126
|
#: users/templates/users/user_profile.html:134
|
||||||
msgid "Quick modify"
|
msgid "Quick modify"
|
||||||
msgstr "快速修改"
|
msgstr "快速修改"
|
||||||
|
|
||||||
|
@ -873,7 +865,7 @@ msgstr "快速修改"
|
||||||
#: perms/templates/perms/asset_permission_list.html:59
|
#: perms/templates/perms/asset_permission_list.html:59
|
||||||
#: terminal/templates/terminal/terminal_list.html:34
|
#: terminal/templates/terminal/terminal_list.html:34
|
||||||
#: users/templates/users/_select_user_modal.html:18
|
#: users/templates/users/_select_user_modal.html:18
|
||||||
#: users/templates/users/user_detail.html:128
|
#: users/templates/users/user_detail.html:136
|
||||||
#: users/templates/users/user_granted_asset.html:46
|
#: users/templates/users/user_granted_asset.html:46
|
||||||
#: users/templates/users/user_group_granted_asset.html:46
|
#: users/templates/users/user_group_granted_asset.html:46
|
||||||
#: users/templates/users/user_list.html:27
|
#: users/templates/users/user_list.html:27
|
||||||
|
@ -890,7 +882,8 @@ msgid "Refresh"
|
||||||
msgstr "刷新"
|
msgstr "刷新"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_detail.html:300
|
#: assets/templates/assets/asset_detail.html:300
|
||||||
#: users/templates/users/user_detail.html:273
|
#: users/templates/users/user_detail.html:282
|
||||||
|
#: users/templates/users/user_detail.html:304
|
||||||
msgid "Update successfully!"
|
msgid "Update successfully!"
|
||||||
msgstr "更新成功"
|
msgstr "更新成功"
|
||||||
|
|
||||||
|
@ -978,8 +971,8 @@ msgstr "存在资产,不能删除"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_list.html:595
|
#: assets/templates/assets/asset_list.html:595
|
||||||
#: assets/templates/assets/system_user_list.html:133
|
#: assets/templates/assets/system_user_list.html:133
|
||||||
#: users/templates/users/user_detail.html:334
|
#: users/templates/users/user_detail.html:352
|
||||||
#: users/templates/users/user_detail.html:359
|
#: users/templates/users/user_detail.html:377
|
||||||
#: users/templates/users/user_group_list.html:81
|
#: users/templates/users/user_group_list.html:81
|
||||||
#: users/templates/users/user_list.html:191
|
#: users/templates/users/user_list.html:191
|
||||||
msgid "Are you sure?"
|
msgid "Are you sure?"
|
||||||
|
@ -1032,7 +1025,7 @@ msgstr "网关列表"
|
||||||
msgid "Create gateway"
|
msgid "Create gateway"
|
||||||
msgstr "创建网关"
|
msgstr "创建网关"
|
||||||
|
|
||||||
#: assets/templates/assets/domain_gateway_list.html:92
|
#: assets/templates/assets/domain_gateway_list.html:87
|
||||||
#: common/templates/common/email_setting.html:58
|
#: common/templates/common/email_setting.html:58
|
||||||
#: common/templates/common/ldap_setting.html:58
|
#: common/templates/common/ldap_setting.html:58
|
||||||
msgid "Test connection"
|
msgid "Test connection"
|
||||||
|
@ -1242,14 +1235,18 @@ msgstr "<b>%(name)s</b> 创建成功"
|
||||||
msgid "<b>%(name)s</b> was updated successfully"
|
msgid "<b>%(name)s</b> was updated successfully"
|
||||||
msgstr "<b>%(name)s</b> 更新成功"
|
msgstr "<b>%(name)s</b> 更新成功"
|
||||||
|
|
||||||
#: common/fields.py:26
|
#: common/fields.py:30
|
||||||
msgid "Not a valid json"
|
msgid "Not a valid json"
|
||||||
msgstr "不是合法json"
|
msgstr "不是合法json"
|
||||||
|
|
||||||
#: common/fields.py:28
|
#: common/fields.py:32
|
||||||
msgid "Not a string type"
|
msgid "Not a string type"
|
||||||
msgstr "不是字符类型"
|
msgstr "不是字符类型"
|
||||||
|
|
||||||
|
#: common/fields.py:69
|
||||||
|
msgid "Encrypt field using Secret Key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: common/forms.py:70
|
#: common/forms.py:70
|
||||||
msgid "Current SITE URL"
|
msgid "Current SITE URL"
|
||||||
msgstr "当前站点URL"
|
msgstr "当前站点URL"
|
||||||
|
@ -1389,7 +1386,7 @@ msgstr ""
|
||||||
msgid "discard time"
|
msgid "discard time"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: common/models.py:29
|
#: common/models.py:29 users/templates/users/user_detail.html:96
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
msgstr "启用"
|
msgstr "启用"
|
||||||
|
|
||||||
|
@ -1707,8 +1704,8 @@ msgstr "任务列表"
|
||||||
msgid "Task run history"
|
msgid "Task run history"
|
||||||
msgstr "执行历史"
|
msgstr "执行历史"
|
||||||
|
|
||||||
#: perms/forms.py:18 users/forms.py:176 users/forms.py:181 users/forms.py:193
|
#: perms/forms.py:18 users/forms.py:188 users/forms.py:193 users/forms.py:205
|
||||||
#: users/forms.py:223
|
#: users/forms.py:235
|
||||||
msgid "Select users"
|
msgid "Select users"
|
||||||
msgstr "选择用户"
|
msgstr "选择用户"
|
||||||
|
|
||||||
|
@ -1717,7 +1714,7 @@ msgstr "选择用户"
|
||||||
#: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14
|
#: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14
|
||||||
#: users/models/group.py:25 users/models/user.py:42
|
#: users/models/group.py:25 users/models/user.py:42
|
||||||
#: users/templates/users/_select_user_modal.html:16
|
#: users/templates/users/_select_user_modal.html:16
|
||||||
#: users/templates/users/user_detail.html:179
|
#: users/templates/users/user_detail.html:188
|
||||||
#: users/templates/users/user_list.html:26
|
#: users/templates/users/user_list.html:26
|
||||||
msgid "User group"
|
msgid "User group"
|
||||||
msgstr "用户组"
|
msgstr "用户组"
|
||||||
|
@ -1732,8 +1729,8 @@ msgstr ""
|
||||||
|
|
||||||
#: perms/models.py:27 perms/models.py:71
|
#: perms/models.py:27 perms/models.py:71
|
||||||
#: perms/templates/perms/asset_permission_detail.html:90
|
#: perms/templates/perms/asset_permission_detail.html:90
|
||||||
#: users/models/user.py:54 users/templates/users/user_detail.html:95
|
#: users/models/user.py:54 users/templates/users/user_detail.html:103
|
||||||
#: users/templates/users/user_profile.html:96
|
#: users/templates/users/user_profile.html:104
|
||||||
msgid "Date expired"
|
msgid "Date expired"
|
||||||
msgstr "失效日期"
|
msgstr "失效日期"
|
||||||
|
|
||||||
|
@ -1770,7 +1767,7 @@ msgid "Add node to this permission"
|
||||||
msgstr "添加节点"
|
msgstr "添加节点"
|
||||||
|
|
||||||
#: perms/templates/perms/asset_permission_asset.html:125
|
#: perms/templates/perms/asset_permission_asset.html:125
|
||||||
#: users/templates/users/user_detail.html:196
|
#: users/templates/users/user_detail.html:205
|
||||||
msgid "Join"
|
msgid "Join"
|
||||||
msgstr "加入"
|
msgstr "加入"
|
||||||
|
|
||||||
|
@ -1856,13 +1853,13 @@ msgstr "商业支持"
|
||||||
msgid "Docs"
|
msgid "Docs"
|
||||||
msgstr "文档"
|
msgstr "文档"
|
||||||
|
|
||||||
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:93
|
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:105
|
||||||
#: users/templates/users/_user.html:36
|
#: users/templates/users/_user.html:36
|
||||||
#: users/templates/users/user_password_update.html:37
|
#: users/templates/users/user_password_update.html:37
|
||||||
#: users/templates/users/user_profile.html:17
|
#: users/templates/users/user_profile.html:17
|
||||||
#: users/templates/users/user_profile_update.html:37
|
#: users/templates/users/user_profile_update.html:37
|
||||||
#: users/templates/users/user_profile_update.html:57
|
#: users/templates/users/user_profile_update.html:57
|
||||||
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:318
|
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:322
|
||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr "个人信息"
|
msgstr "个人信息"
|
||||||
|
|
||||||
|
@ -1919,13 +1916,13 @@ msgstr "关闭"
|
||||||
|
|
||||||
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
|
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
|
||||||
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
|
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
|
||||||
#: users/views/login.py:205 users/views/login.py:254 users/views/user.py:60
|
#: users/views/login.py:240 users/views/login.py:289 users/views/user.py:64
|
||||||
#: users/views/user.py:75 users/views/user.py:95 users/views/user.py:151
|
#: users/views/user.py:79 users/views/user.py:99 users/views/user.py:155
|
||||||
#: users/views/user.py:306 users/views/user.py:353 users/views/user.py:375
|
#: users/views/user.py:310 users/views/user.py:357 users/views/user.py:379
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "用户管理"
|
msgstr "用户管理"
|
||||||
|
|
||||||
#: templates/_nav.html:13 users/views/user.py:61
|
#: templates/_nav.html:13 users/views/user.py:65
|
||||||
msgid "User list"
|
msgid "User list"
|
||||||
msgstr "用户列表"
|
msgstr "用户列表"
|
||||||
|
|
||||||
|
@ -2231,7 +2228,11 @@ msgstr ""
|
||||||
msgid "Invalid token or cache refreshed."
|
msgid "Invalid token or cache refreshed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: users/forms.py:27 users/models/user.py:43
|
#: users/forms.py:30
|
||||||
|
msgid "Otp_code"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: users/forms.py:39 users/models/user.py:43
|
||||||
#: users/templates/users/_select_user_modal.html:15
|
#: users/templates/users/_select_user_modal.html:15
|
||||||
#: users/templates/users/user_detail.html:87
|
#: users/templates/users/user_detail.html:87
|
||||||
#: users/templates/users/user_list.html:25
|
#: users/templates/users/user_list.html:25
|
||||||
|
@ -2239,57 +2240,57 @@ msgstr ""
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
msgstr "角色"
|
msgstr "角色"
|
||||||
|
|
||||||
#: users/forms.py:29 users/forms.py:139
|
#: users/forms.py:41 users/forms.py:151
|
||||||
msgid "ssh public key"
|
msgid "ssh public key"
|
||||||
msgstr "ssh公钥"
|
msgstr "ssh公钥"
|
||||||
|
|
||||||
#: users/forms.py:30 users/forms.py:140
|
#: users/forms.py:42 users/forms.py:152
|
||||||
msgid "ssh-rsa AAAA..."
|
msgid "ssh-rsa AAAA..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: users/forms.py:31
|
#: users/forms.py:43
|
||||||
msgid "Paste user id_rsa.pub here."
|
msgid "Paste user id_rsa.pub here."
|
||||||
msgstr "复制用户公钥到这里"
|
msgstr "复制用户公钥到这里"
|
||||||
|
|
||||||
#: users/forms.py:49 users/templates/users/user_detail.html:187
|
#: users/forms.py:61 users/templates/users/user_detail.html:196
|
||||||
msgid "Join user groups"
|
msgid "Join user groups"
|
||||||
msgstr "添加到用户组"
|
msgstr "添加到用户组"
|
||||||
|
|
||||||
#: users/forms.py:59 users/forms.py:154
|
#: users/forms.py:71 users/forms.py:166
|
||||||
msgid "Public key should not be the same as your old one."
|
msgid "Public key should not be the same as your old one."
|
||||||
msgstr "不能和原来的密钥相同"
|
msgstr "不能和原来的密钥相同"
|
||||||
|
|
||||||
#: users/forms.py:63 users/forms.py:158 users/serializers.py:42
|
#: users/forms.py:75 users/forms.py:170 users/serializers.py:45
|
||||||
msgid "Not a valid ssh public key"
|
msgid "Not a valid ssh public key"
|
||||||
msgstr "ssh密钥不合法"
|
msgstr "ssh密钥不合法"
|
||||||
|
|
||||||
#: users/forms.py:99
|
#: users/forms.py:111
|
||||||
msgid "Old password"
|
msgid "Old password"
|
||||||
msgstr "原来密码"
|
msgstr "原来密码"
|
||||||
|
|
||||||
#: users/forms.py:104
|
#: users/forms.py:116
|
||||||
msgid "New password"
|
msgid "New password"
|
||||||
msgstr "新密码"
|
msgstr "新密码"
|
||||||
|
|
||||||
#: users/forms.py:109
|
#: users/forms.py:121
|
||||||
msgid "Confirm password"
|
msgid "Confirm password"
|
||||||
msgstr "确认密码"
|
msgstr "确认密码"
|
||||||
|
|
||||||
#: users/forms.py:119
|
#: users/forms.py:131
|
||||||
msgid "Old password error"
|
msgid "Old password error"
|
||||||
msgstr "原来密码错误"
|
msgstr "原来密码错误"
|
||||||
|
|
||||||
#: users/forms.py:127
|
#: users/forms.py:139
|
||||||
msgid "Password does not match"
|
msgid "Password does not match"
|
||||||
msgstr "密码不一致"
|
msgstr "密码不一致"
|
||||||
|
|
||||||
#: users/forms.py:141
|
#: users/forms.py:153
|
||||||
msgid "Paste your id_rsa.pub here."
|
msgid "Paste your id_rsa.pub here."
|
||||||
msgstr "复制你的公钥到这里"
|
msgstr "复制你的公钥到这里"
|
||||||
|
|
||||||
#: users/forms.py:169 users/models/user.py:51
|
#: users/forms.py:181 users/models/user.py:51
|
||||||
#: users/templates/users/user_password_update.html:43
|
#: users/templates/users/user_password_update.html:43
|
||||||
#: users/templates/users/user_profile.html:71
|
#: users/templates/users/user_profile.html:79
|
||||||
#: users/templates/users/user_profile_update.html:43
|
#: users/templates/users/user_profile_update.html:43
|
||||||
#: users/templates/users/user_pubkey_update.html:43
|
#: users/templates/users/user_pubkey_update.html:43
|
||||||
msgid "Public key"
|
msgid "Public key"
|
||||||
|
@ -2319,7 +2320,7 @@ msgstr "Agent"
|
||||||
msgid "Date login"
|
msgid "Date login"
|
||||||
msgstr "登录日期"
|
msgstr "登录日期"
|
||||||
|
|
||||||
#: users/models/user.py:29 users/models/user.py:281
|
#: users/models/user.py:29 users/models/user.py:295
|
||||||
msgid "Administrator"
|
msgid "Administrator"
|
||||||
msgstr "管理员"
|
msgstr "管理员"
|
||||||
|
|
||||||
|
@ -2327,15 +2328,18 @@ msgstr "管理员"
|
||||||
msgid "Application"
|
msgid "Application"
|
||||||
msgstr "应用程序"
|
msgstr "应用程序"
|
||||||
|
|
||||||
#: users/models/user.py:34
|
#: users/models/user.py:34 users/templates/users/user_profile.html:74
|
||||||
|
#: users/templates/users/user_profile.html:155
|
||||||
|
#: users/templates/users/user_profile.html:158
|
||||||
msgid "Disable"
|
msgid "Disable"
|
||||||
msgstr "禁用"
|
msgstr "禁用"
|
||||||
|
|
||||||
#: users/models/user.py:35
|
#: users/models/user.py:35 users/templates/users/user_profile.html:72
|
||||||
|
#: users/templates/users/user_profile.html:162
|
||||||
msgid "Enable"
|
msgid "Enable"
|
||||||
msgstr "启用"
|
msgstr "启用"
|
||||||
|
|
||||||
#: users/models/user.py:36
|
#: users/models/user.py:36 users/templates/users/user_profile.html:70
|
||||||
msgid "Force enable"
|
msgid "Force enable"
|
||||||
msgstr "强制启用"
|
msgstr "强制启用"
|
||||||
|
|
||||||
|
@ -2352,11 +2356,11 @@ msgstr "头像"
|
||||||
msgid "Wechat"
|
msgid "Wechat"
|
||||||
msgstr "微信"
|
msgstr "微信"
|
||||||
|
|
||||||
#: users/models/user.py:47
|
#: users/models/user.py:47 users/templates/users/user_detail.html:91
|
||||||
msgid "Enable OTP"
|
msgid "Enable OTP"
|
||||||
msgstr "二次验证"
|
msgstr "二次验证"
|
||||||
|
|
||||||
#: users/models/user.py:284
|
#: users/models/user.py:298
|
||||||
msgid "Administrator is the super user of system"
|
msgid "Administrator is the super user of system"
|
||||||
msgstr "Administrator是初始的超级管理员"
|
msgstr "Administrator是初始的超级管理员"
|
||||||
|
|
||||||
|
@ -2408,11 +2412,16 @@ msgstr "Step"
|
||||||
|
|
||||||
#: users/templates/users/first_login.html:57
|
#: users/templates/users/first_login.html:57
|
||||||
msgid "Previous"
|
msgid "Previous"
|
||||||
msgstr ""
|
msgstr "上一步"
|
||||||
|
|
||||||
#: users/templates/users/first_login.html:60
|
#: users/templates/users/first_login.html:60
|
||||||
|
#: users/templates/users/login_otp.html:66
|
||||||
|
#: users/templates/users/user_otp_authentication.html:22
|
||||||
|
#: users/templates/users/user_otp_enable_bind.html:25
|
||||||
|
#: users/templates/users/user_otp_enable_install_app.html:22
|
||||||
|
#: users/templates/users/user_password_authentication.html:21
|
||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr ""
|
msgstr "下一步"
|
||||||
|
|
||||||
#: users/templates/users/first_login_done.html:30
|
#: users/templates/users/first_login_done.html:30
|
||||||
msgid "Welcome to use jumpserver, visit "
|
msgid "Welcome to use jumpserver, visit "
|
||||||
|
@ -2447,8 +2456,22 @@ msgstr "Agent"
|
||||||
msgid "City"
|
msgid "City"
|
||||||
msgstr "城市"
|
msgstr "城市"
|
||||||
|
|
||||||
|
#: users/templates/users/login_otp.html:45
|
||||||
|
msgid "二次认证"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: users/templates/users/login_otp.html:64
|
||||||
|
#: users/templates/users/user_otp_authentication.html:19
|
||||||
|
#: users/templates/users/user_otp_enable_bind.html:18
|
||||||
|
msgid "Six figures"
|
||||||
|
msgstr "6位数字"
|
||||||
|
|
||||||
|
#: users/templates/users/login_otp.html:69
|
||||||
|
msgid "Can't provide security? Please contact the administrator"
|
||||||
|
msgstr "如果不能提供OTP码,请联系管理员"
|
||||||
|
|
||||||
#: users/templates/users/reset_password.html:45
|
#: users/templates/users/reset_password.html:45
|
||||||
#: users/templates/users/user_detail.html:325 users/utils.py:71
|
#: users/templates/users/user_detail.html:343 users/utils.py:72
|
||||||
msgid "Reset password"
|
msgid "Reset password"
|
||||||
msgstr "重置密码"
|
msgstr "重置密码"
|
||||||
|
|
||||||
|
@ -2456,8 +2479,13 @@ msgstr "重置密码"
|
||||||
msgid "Password again"
|
msgid "Password again"
|
||||||
msgstr "再次输入密码"
|
msgstr "再次输入密码"
|
||||||
|
|
||||||
|
#: users/templates/users/reset_password.html:57
|
||||||
|
#: users/templates/users/user_profile.html:20
|
||||||
|
msgid "Setting"
|
||||||
|
msgstr "设置"
|
||||||
|
|
||||||
#: users/templates/users/user_create.html:4
|
#: users/templates/users/user_create.html:4
|
||||||
#: users/templates/users/user_list.html:16 users/views/user.py:75
|
#: users/templates/users/user_list.html:16 users/views/user.py:79
|
||||||
msgid "Create user"
|
msgid "Create user"
|
||||||
msgstr "创建用户"
|
msgstr "创建用户"
|
||||||
|
|
||||||
|
@ -2466,7 +2494,7 @@ msgid "Reset link will be generated and sent to the user. "
|
||||||
msgstr "生成重置密码连接,通过邮件发送给用户"
|
msgstr "生成重置密码连接,通过邮件发送给用户"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:19
|
#: users/templates/users/user_detail.html:19
|
||||||
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:152
|
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:156
|
||||||
msgid "User detail"
|
msgid "User detail"
|
||||||
msgstr "用户详情"
|
msgstr "用户详情"
|
||||||
|
|
||||||
|
@ -2477,55 +2505,67 @@ msgstr "用户详情"
|
||||||
msgid "Asset granted"
|
msgid "Asset granted"
|
||||||
msgstr "授权的资产"
|
msgstr "授权的资产"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:107
|
#: users/templates/users/user_detail.html:94
|
||||||
#: users/templates/users/user_profile.html:92
|
msgid "Force enabled"
|
||||||
|
msgstr "强制启用"
|
||||||
|
|
||||||
|
#: users/templates/users/user_detail.html:98
|
||||||
|
msgid "Disabled"
|
||||||
|
msgstr "禁用"
|
||||||
|
|
||||||
|
#: users/templates/users/user_detail.html:115
|
||||||
|
#: users/templates/users/user_profile.html:100
|
||||||
msgid "Last login"
|
msgid "Last login"
|
||||||
msgstr "最后登录"
|
msgstr "最后登录"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:157
|
#: users/templates/users/user_detail.html:151
|
||||||
|
msgid "Force enabled OTP"
|
||||||
|
msgstr "强制启用OTP"
|
||||||
|
|
||||||
|
#: users/templates/users/user_detail.html:166
|
||||||
msgid "Send reset password mail"
|
msgid "Send reset password mail"
|
||||||
msgstr "发送重置密码邮件"
|
msgstr "发送重置密码邮件"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:160
|
#: users/templates/users/user_detail.html:169
|
||||||
#: users/templates/users/user_detail.html:168
|
#: users/templates/users/user_detail.html:177
|
||||||
msgid "Send"
|
msgid "Send"
|
||||||
msgstr "发送"
|
msgstr "发送"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:165
|
#: users/templates/users/user_detail.html:174
|
||||||
msgid "Send reset ssh key mail"
|
msgid "Send reset ssh key mail"
|
||||||
msgstr "发送重置密钥邮件"
|
msgstr "发送重置密钥邮件"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:324
|
#: users/templates/users/user_detail.html:342
|
||||||
msgid "An e-mail has been sent to the user`s mailbox."
|
msgid "An e-mail has been sent to the user`s mailbox."
|
||||||
msgstr "已发送邮件到用户邮箱"
|
msgstr "已发送邮件到用户邮箱"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:335
|
#: users/templates/users/user_detail.html:353
|
||||||
msgid "This will reset the user password and send a reset mail"
|
msgid "This will reset the user password and send a reset mail"
|
||||||
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
|
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:349
|
#: users/templates/users/user_detail.html:367
|
||||||
msgid ""
|
msgid ""
|
||||||
"The reset-ssh-public-key E-mail has been sent successfully. Please inform "
|
"The reset-ssh-public-key E-mail has been sent successfully. Please inform "
|
||||||
"the user to update his new ssh public key."
|
"the user to update his new ssh public key."
|
||||||
msgstr "重设密钥邮件将会发送到用户邮箱"
|
msgstr "重设密钥邮件将会发送到用户邮箱"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:350
|
#: users/templates/users/user_detail.html:368
|
||||||
msgid "Reset SSH public key"
|
msgid "Reset SSH public key"
|
||||||
msgstr "重置SSH密钥"
|
msgstr "重置SSH密钥"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:360
|
#: users/templates/users/user_detail.html:378
|
||||||
msgid "This will reset the user public key and send a reset mail"
|
msgid "This will reset the user public key and send a reset mail"
|
||||||
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
|
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:377
|
#: users/templates/users/user_detail.html:395
|
||||||
#: users/templates/users/user_profile.html:174
|
#: users/templates/users/user_profile.html:203
|
||||||
msgid "Successfully updated the SSH public key."
|
msgid "Successfully updated the SSH public key."
|
||||||
msgstr "更新ssh密钥成功"
|
msgstr "更新ssh密钥成功"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:378
|
#: users/templates/users/user_detail.html:396
|
||||||
#: users/templates/users/user_detail.html:382
|
#: users/templates/users/user_detail.html:400
|
||||||
#: users/templates/users/user_profile.html:175
|
#: users/templates/users/user_profile.html:204
|
||||||
#: users/templates/users/user_profile.html:180
|
#: users/templates/users/user_profile.html:209
|
||||||
msgid "User SSH public key update"
|
msgid "User SSH public key update"
|
||||||
msgstr "ssh密钥"
|
msgstr "ssh密钥"
|
||||||
|
|
||||||
|
@ -2585,24 +2625,28 @@ msgstr "用户删除失败"
|
||||||
msgid "OTP"
|
msgid "OTP"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: users/templates/users/user_profile.html:100 users/views/user.py:181
|
#: users/templates/users/user_profile.html:108 users/views/user.py:185
|
||||||
#: users/views/user.py:235
|
#: users/views/user.py:239
|
||||||
msgid "User groups"
|
msgid "User groups"
|
||||||
msgstr "用户组"
|
msgstr "用户组"
|
||||||
|
|
||||||
#: users/templates/users/user_profile.html:132
|
#: users/templates/users/user_profile.html:140
|
||||||
msgid "Update password"
|
msgid "Update password"
|
||||||
msgstr "更改密码"
|
msgstr "更改密码"
|
||||||
|
|
||||||
#: users/templates/users/user_profile.html:140
|
#: users/templates/users/user_profile.html:148
|
||||||
|
msgid "Update otp"
|
||||||
|
msgstr "更改OTP设置"
|
||||||
|
|
||||||
|
#: users/templates/users/user_profile.html:169
|
||||||
msgid "Update SSH public key"
|
msgid "Update SSH public key"
|
||||||
msgstr "更改SSH密钥"
|
msgstr "更改SSH密钥"
|
||||||
|
|
||||||
#: users/templates/users/user_profile.html:148
|
#: users/templates/users/user_profile.html:177
|
||||||
msgid "Reset public key and download"
|
msgid "Reset public key and download"
|
||||||
msgstr "重置并下载SSH密钥"
|
msgstr "重置并下载SSH密钥"
|
||||||
|
|
||||||
#: users/templates/users/user_profile.html:178
|
#: users/templates/users/user_profile.html:207
|
||||||
msgid "Failed to update SSH public key."
|
msgid "Failed to update SSH public key."
|
||||||
msgstr "更新密钥失败"
|
msgstr "更新密钥失败"
|
||||||
|
|
||||||
|
@ -2622,15 +2666,15 @@ msgstr "更新密钥"
|
||||||
msgid "Or reset by server"
|
msgid "Or reset by server"
|
||||||
msgstr "或者重置并下载密钥"
|
msgstr "或者重置并下载密钥"
|
||||||
|
|
||||||
#: users/templates/users/user_update.html:4 users/views/user.py:95
|
#: users/templates/users/user_update.html:4 users/views/user.py:99
|
||||||
msgid "Update user"
|
msgid "Update user"
|
||||||
msgstr "更新用户"
|
msgstr "更新用户"
|
||||||
|
|
||||||
#: users/utils.py:35
|
#: users/utils.py:36
|
||||||
msgid "Create account successfully"
|
msgid "Create account successfully"
|
||||||
msgstr "创建账户成功"
|
msgstr "创建账户成功"
|
||||||
|
|
||||||
#: users/utils.py:37
|
#: users/utils.py:38
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -2671,7 +2715,7 @@ msgstr ""
|
||||||
" </br>\n"
|
" </br>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: users/utils.py:73
|
#: users/utils.py:74
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -2715,11 +2759,11 @@ msgstr ""
|
||||||
" </br>\n"
|
" </br>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: users/utils.py:104
|
#: users/utils.py:105
|
||||||
msgid "SSH Key Reset"
|
msgid "SSH Key Reset"
|
||||||
msgstr "重置ssh密钥"
|
msgstr "重置ssh密钥"
|
||||||
|
|
||||||
#: users/utils.py:106
|
#: users/utils.py:107
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -2744,15 +2788,15 @@ msgstr ""
|
||||||
" </br>\n"
|
" </br>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: users/utils.py:139
|
#: users/utils.py:140
|
||||||
msgid "User not exist"
|
msgid "User not exist"
|
||||||
msgstr "用户不存在"
|
msgstr "用户不存在"
|
||||||
|
|
||||||
#: users/utils.py:141
|
#: users/utils.py:142
|
||||||
msgid "Disabled or expired"
|
msgid "Disabled or expired"
|
||||||
msgstr "禁用或失效"
|
msgstr "禁用或失效"
|
||||||
|
|
||||||
#: users/utils.py:154
|
#: users/utils.py:155
|
||||||
msgid "Password or SSH public key invalid"
|
msgid "Password or SSH public key invalid"
|
||||||
msgstr "密码或密钥不合法"
|
msgstr "密码或密钥不合法"
|
||||||
|
|
||||||
|
@ -2768,78 +2812,102 @@ msgstr "更新用户组"
|
||||||
msgid "User group granted asset"
|
msgid "User group granted asset"
|
||||||
msgstr "用户组授权资产"
|
msgstr "用户组授权资产"
|
||||||
|
|
||||||
#: users/views/login.py:55
|
#: users/views/login.py:56
|
||||||
msgid "Please enable cookies and try again."
|
msgid "Please enable cookies and try again."
|
||||||
msgstr "设置你的浏览器支持cookie"
|
msgstr "设置你的浏览器支持cookie"
|
||||||
|
|
||||||
#: users/views/login.py:97
|
#: users/views/login.py:106 users/views/user.py:460 users/views/user.py:485
|
||||||
|
msgid "Otp code invalid"
|
||||||
|
msgstr "otp码认证失败"
|
||||||
|
|
||||||
|
#: users/views/login.py:132
|
||||||
msgid "Logout success"
|
msgid "Logout success"
|
||||||
msgstr "退出登录成功"
|
msgstr "退出登录成功"
|
||||||
|
|
||||||
#: users/views/login.py:98
|
#: users/views/login.py:133
|
||||||
msgid "Logout success, return login page"
|
msgid "Logout success, return login page"
|
||||||
msgstr "退出登录成功,返回到登录页面"
|
msgstr "退出登录成功,返回到登录页面"
|
||||||
|
|
||||||
#: users/views/login.py:114
|
#: users/views/login.py:149
|
||||||
msgid "Email address invalid, please input again"
|
msgid "Email address invalid, please input again"
|
||||||
msgstr "邮箱地址错误,重新输入"
|
msgstr "邮箱地址错误,重新输入"
|
||||||
|
|
||||||
#: users/views/login.py:127
|
#: users/views/login.py:162
|
||||||
msgid "Send reset password message"
|
msgid "Send reset password message"
|
||||||
msgstr "发送重置密码邮件"
|
msgstr "发送重置密码邮件"
|
||||||
|
|
||||||
#: users/views/login.py:128
|
#: users/views/login.py:163
|
||||||
msgid "Send reset password mail success, login your mail box and follow it "
|
msgid "Send reset password mail success, login your mail box and follow it "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
|
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
|
||||||
|
|
||||||
#: users/views/login.py:142
|
#: users/views/login.py:177
|
||||||
msgid "Reset password success"
|
msgid "Reset password success"
|
||||||
msgstr "重置密码成功"
|
msgstr "重置密码成功"
|
||||||
|
|
||||||
#: users/views/login.py:143
|
#: users/views/login.py:178
|
||||||
msgid "Reset password success, return to login page"
|
msgid "Reset password success, return to login page"
|
||||||
msgstr "重置密码成功,返回到登录页面"
|
msgstr "重置密码成功,返回到登录页面"
|
||||||
|
|
||||||
#: users/views/login.py:160 users/views/login.py:173
|
#: users/views/login.py:195 users/views/login.py:208
|
||||||
msgid "Token invalid or expired"
|
msgid "Token invalid or expired"
|
||||||
msgstr "Token错误或失效"
|
msgstr "Token错误或失效"
|
||||||
|
|
||||||
#: users/views/login.py:169
|
#: users/views/login.py:204
|
||||||
msgid "Password not same"
|
msgid "Password not same"
|
||||||
msgstr "密码不一致"
|
msgstr "密码不一致"
|
||||||
|
|
||||||
#: users/views/login.py:205
|
#: users/views/login.py:240
|
||||||
msgid "First login"
|
msgid "First login"
|
||||||
msgstr "首次登陆"
|
msgstr "首次登陆"
|
||||||
|
|
||||||
#: users/views/login.py:255
|
#: users/views/login.py:290
|
||||||
msgid "Login log list"
|
msgid "Login log list"
|
||||||
msgstr "登录日志"
|
msgstr "登录日志"
|
||||||
|
|
||||||
#: users/views/user.py:105
|
#: users/views/user.py:109
|
||||||
msgid "Bulk update user success"
|
msgid "Bulk update user success"
|
||||||
msgstr "批量更新用户成功"
|
msgstr "批量更新用户成功"
|
||||||
|
|
||||||
#: users/views/user.py:210
|
#: users/views/user.py:214
|
||||||
msgid "Invalid file."
|
msgid "Invalid file."
|
||||||
msgstr "文件不合法"
|
msgstr "文件不合法"
|
||||||
|
|
||||||
#: users/views/user.py:307
|
#: users/views/user.py:311
|
||||||
msgid "User granted assets"
|
msgid "User granted assets"
|
||||||
msgstr "用户授权资产"
|
msgstr "用户授权资产"
|
||||||
|
|
||||||
#: users/views/user.py:336
|
#: users/views/user.py:340
|
||||||
msgid "Profile setting"
|
msgid "Profile setting"
|
||||||
msgstr "个人信息设置"
|
msgstr "个人信息设置"
|
||||||
|
|
||||||
#: users/views/user.py:354
|
#: users/views/user.py:358
|
||||||
msgid "Password update"
|
msgid "Password update"
|
||||||
msgstr "密码更新"
|
msgstr "密码更新"
|
||||||
|
|
||||||
#: users/views/user.py:376
|
#: users/views/user.py:380
|
||||||
msgid "Public key update"
|
msgid "Public key update"
|
||||||
msgstr "密钥更新"
|
msgstr "密钥更新"
|
||||||
|
|
||||||
|
#: users/views/user.py:419
|
||||||
|
msgid "Password invalid"
|
||||||
|
msgstr "用户名或密码无效"
|
||||||
|
|
||||||
|
#: users/views/user.py:512
|
||||||
|
msgid "OTP enable success"
|
||||||
|
msgstr "OTP 绑定成功"
|
||||||
|
|
||||||
|
#: users/views/user.py:513
|
||||||
|
msgid "OTP enable success, return login page"
|
||||||
|
msgstr "OTP 绑定成功,返回到登录页面"
|
||||||
|
|
||||||
|
#: users/views/user.py:515
|
||||||
|
msgid "OTP disable success"
|
||||||
|
msgstr "OTP 解绑成功"
|
||||||
|
|
||||||
|
#: users/views/user.py:516
|
||||||
|
msgid "OTP disable success, return login page"
|
||||||
|
msgstr "OTP 解绑成功,返回登录页面"
|
||||||
|
|
||||||
#~ msgid "Add asset"
|
#~ msgid "Add asset"
|
||||||
#~ msgstr "添加资产到节点"
|
#~ msgstr "添加资产到节点"
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*公共样式*/
|
||||||
|
*{
|
||||||
|
margin:0;
|
||||||
|
padding: 0;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
text-decoration: none;
|
||||||
|
color:black
|
||||||
|
}
|
||||||
|
li{
|
||||||
|
list-style:none;
|
||||||
|
}
|
||||||
|
button{
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.red-fonts{
|
||||||
|
color: #ed5565;
|
||||||
|
font-size: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*header样式*/
|
||||||
|
header{
|
||||||
|
overflow:hidden ;
|
||||||
|
background: #dedede;
|
||||||
|
padding:15px 200px;
|
||||||
|
}
|
||||||
|
header .logo a{
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
header .logo a:nth-child(2){
|
||||||
|
padding-top: 13px;
|
||||||
|
}
|
||||||
|
header div:nth-child(1){
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
header div:nth-child(2){
|
||||||
|
float:right;
|
||||||
|
font-size: 12px;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
header div:nth-child(2) a:hover{
|
||||||
|
color:#1ab394;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*article样式*/
|
||||||
|
article{
|
||||||
|
padding-top: 50px;
|
||||||
|
padding:50px 370px
|
||||||
|
}
|
||||||
|
article ul{
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
left: 50%;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
article ul li{
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
right: 50%;
|
||||||
|
}
|
||||||
|
article ul li span,article ul li i{
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
article ul li span{
|
||||||
|
width: 150px;
|
||||||
|
height: 4px;
|
||||||
|
margin: 15px 0;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
article ul li:last-child{
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
.iconfont{
|
||||||
|
font-size: 30px;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
.back{
|
||||||
|
margin-left:-15px;
|
||||||
|
}
|
||||||
|
.active{
|
||||||
|
color:#1ab394;
|
||||||
|
}
|
||||||
|
.clearfix:after {
|
||||||
|
content:"";
|
||||||
|
height:0;
|
||||||
|
visibility:hidden;
|
||||||
|
display:block;
|
||||||
|
clear:both;
|
||||||
|
}
|
||||||
|
.verify{
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
/*padding-left:70px;*/
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
.verify span{
|
||||||
|
color:red;
|
||||||
|
}
|
||||||
|
.line{
|
||||||
|
width: 500px;
|
||||||
|
height:1px;
|
||||||
|
margin-left:100px;
|
||||||
|
margin-top:10px ;
|
||||||
|
background: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*输入框样式*/
|
||||||
|
.form-input{
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
.form-input input{
|
||||||
|
width: 200px;
|
||||||
|
height: 30px;
|
||||||
|
padding-left: 10px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
/*身份验证*/
|
||||||
|
/*安装应用*/
|
||||||
|
.verify div{
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.verify div:nth-child(3){
|
||||||
|
margin-left: 58px;
|
||||||
|
}
|
||||||
|
.next{
|
||||||
|
margin: 20px auto;
|
||||||
|
display: block;
|
||||||
|
width: 214px;
|
||||||
|
line-height: 34px;
|
||||||
|
background: #1ab394;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
/*绑定TOTP*/
|
||||||
|
|
||||||
|
/*版权信息*/
|
||||||
|
footer{
|
||||||
|
text-align:center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
@font-face {font-family: "iconfont";
|
||||||
|
src: url('iconfont.eot?t=1523776860888'); /* IE9*/
|
||||||
|
src: url('iconfont.eot?t=1523776860888#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||||
|
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAY4AAsAAAAACVwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kggY21hcAAAAYAAAAB0AAABuM8DAsdnbHlmAAAB9AAAAjgAAALsJ9wRv2hlYWQAAAQsAAAALwAAADYREYC1aGhlYQAABFwAAAAcAAAAJAfeA4dobXR4AAAEeAAAABMAAAAYF+kAAGxvY2EAAASMAAAADgAAAA4C0gHmbWF4cAAABJwAAAAfAAAAIAEVAF1uYW1lAAAEvAAAAUUAAAJtPlT+fXBvc3QAAAYEAAAANAAAAEtj7FVFeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sM4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDwzYm7438AQw9zA0AAUZgTJAQAoXgyieJzFkc0NgCAMhV/5McYQZBBPHJ3BOTw5ABN3DWwLFyfwka+0LyUQCiAC8MIhBIAeEFS3uGS+x2Z+wCn1KsvJ3rhw7d2yPDMVWUeyzMuZqN204DfRf1d/lSxes9J/bxN5IueBzoL3gc6Dy0D7uQ7gXrxnFIt4nG2Su2/TUBSH77mO7aTNg1zHdt6JbWq3hIbUj7giipNWhRKoAm0BlaWIh5goDCBFSAxZqiBgqNhZUAVTBTuVWlgZO4CyRAj+jd7ipguVcnW330/6vqNzEIvQ0W9ml0kiAU2iGbSAbiAEXAnUKM6BYthlXAJRYUU5EWUMzVB4TS0zdZBVLiGZVVuXOZ6LQRTyYClm1ShjAxzbwzUwpRxAKpNeJRNZwmzBWNLIb9Kr+AOIBS0b86Zp63wjYRaFYCdMSIqQt0GOZYMYB2JR2JClEBsa4+g2G0uLu4UpXIBwykgv3YkUM+TeK/tJbkIOAXS7IGSK0U+NeDru/5dpSSAp/kwkmExHtLMJ6PwdTwrhnP4H+Q/7s3YDiOmicZQ/nhLxEpKryNWRAJx6AcrgVqUCgAeyxGHUpwOWBaXfB4Vl6eAR3RFs4YAhYkqHfWiCnhIJ0/WT/n9Nukl3CDk4Cf3WsH7C/sp8Z+YQQVmfremaEgU+zol5kD1wy8AojhXnHTduwRFgoN+cRcAs3dungQDN04+9Nz97TANg0aEDIuxRdpgdVubhx+TlxuGv0wx7NIPTVMORLLPq2CVwLNOxNZV3qqYkJni/JSZGsB/W7t+2vbbX2rYq7542Z2vv11/MLY1QWTOL1yt1+1nj8YNSS11udlTMt2srp7zOjfYSFcf1/MPRhzrWsY9/VeIImym69aU1c9Fdbl1ZX7lbv/l6hMjzhfnPrVvTtnup7a5dm91Y7YG//n+HyatVeJxjYGRgYADiTatcAuP5bb4ycLMwgMC1n3IxCPp/AwsDcwOQy8HABBIFACp7CkQAeJxjYGRgYG7438AQw8IAAkCSkQEVsAEARwwCb3icY2FgYGB+ycDAwoCKARKfAQEAAAAAAAB2ALQA5gEyAXYAAHicY2BkYGBgYwhkYGUAASYg5gJCBob/YD4DABFIAXMAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicY2BigAAuBuyAjZGJkZmRhZGVkY2RnYGxgi2lNDM9v5SluCS1gBVEGIJJIwYGAIzACOU=') format('woff'),
|
||||||
|
url('iconfont.ttf?t=1523776860888') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
|
||||||
|
url('iconfont.svg?t=1523776860888#iconfont') format('svg'); /* iOS 4.1- */
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-family:"iconfont" !important;
|
||||||
|
font-size:16px;
|
||||||
|
font-style:normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-duigou:before { content: "\e632"; }
|
||||||
|
|
||||||
|
.icon-step:before { content: "\e60e"; }
|
||||||
|
|
||||||
|
.icon-step1:before { content: "\e60f"; }
|
||||||
|
|
||||||
|
.icon-step2:before { content: "\e610"; }
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
(function(window){var svgSprite='<svg><symbol id="icon-duigou" viewBox="0 0 1024 1024"><path d="M512 0C228.266667 0 0 228.266667 0 512c0 283.733333 228.266667 512 512 512 283.733333 0 512-228.266667 512-512C1024 228.266667 795.733333 0 512 0zM832 384 492.8 723.2C469.333333 746.666667 426.666667 746.666667 403.2 723.2L192 512c0 0-32-32 0-64s64 0 64 0l192 192 320-320c0 0 32-32 64 0S832 384 832 384z" ></path></symbol><symbol id="icon-step" viewBox="0 0 1024 1024"><path d="M511.3 64.6h-1.8c-0.7-0.4-1.6-0.7-2.5-0.7H188.3c-69 0-125.5 56.5-125.5 125.5v289.1c-0.9 11.9-1.4 24-1.4 36.1 0 248.5 201.5 450 450 450s450-201.5 450-450-201.5-450-450.1-450z m162.1 724.8H326.5v-66H462V264l-139 40.2v-70.3l215.2-62.5v552h135.2v66z" ></path></symbol><symbol id="icon-step1" viewBox="0 0 1024 1024"><path d="M511.3 64.6h-1.8c-0.7-0.4-1.6-0.7-2.5-0.7H188.3c-69 0-125.5 56.5-125.5 125.5v289.1c-0.9 11.9-1.4 24-1.4 36.1 0 248.5 201.5 450 450 450s450-201.5 450-450-201.5-450-450.1-450z m91.4 684.9c-39.2 33.3-91.3 50-156.4 50-57.3 0-103.5-10.7-138.7-32v-79.3c41.4 32 88.1 48 140.2 48 41.7 0 74.7-10.2 99-30.7 24.3-20.4 36.5-47.9 36.5-82.2 0-76.6-54.8-114.8-164.5-114.8h-50.4v-63.3h48c97.1 0 145.7-35.9 145.7-107.8 0-66.4-37.1-99.6-111.3-99.6-42.5 0-82.4 14.3-119.9 43v-72.3c39.6-22.9 85.8-34.4 138.7-34.4 51.6 0 93 13.5 124.2 40.4s46.9 61.9 46.9 104.9c0 79.2-40.4 130.1-121.1 152.7v1.6c43.8 4.7 78.3 20.1 103.7 46.3s38.1 58.8 38.1 97.9c0 54.4-19.6 98.3-58.7 131.6z" ></path></symbol><symbol id="icon-step2" viewBox="0 0 1024 1024"><path d="M511.3 64.6h-1.8c-0.7-0.4-1.6-0.7-2.5-0.7H188.3c-69 0-125.5 56.5-125.5 125.5v289.1c-0.9 11.9-1.4 24-1.4 36.1 0 248.5 201.5 450 450 450s450-201.5 450-450-201.5-450-450.1-450z m150.8 656.8v68h-368V723l175.8-175.4c48.4-48.4 80.9-86.8 97.3-115 16.4-28.3 24.6-57.5 24.6-87.7 0-34.4-9.6-60.7-28.9-79.1-19.3-18.4-47.1-27.5-83.6-27.5-53.9 0-105.3 22.9-154.3 68.8v-77.7c47.7-36.7 103.1-55.1 166.4-55.1 54.4 0 97.4 14.7 128.9 44.1 31.5 29.4 47.3 69 47.3 118.8 0 37.5-10.1 74.3-30.3 110.4-20.2 36.1-58.4 82-114.6 137.7l-138 134.5v1.6h277.4z" ></path></symbol></svg>';var script=function(){var scripts=document.getElementsByTagName("script");return scripts[scripts.length-1]}();var shouldInjectCss=script.getAttribute("data-injectcss");var ready=function(fn){if(document.addEventListener){if(~["complete","loaded","interactive"].indexOf(document.readyState)){setTimeout(fn,0)}else{var loadFn=function(){document.removeEventListener("DOMContentLoaded",loadFn,false);fn()};document.addEventListener("DOMContentLoaded",loadFn,false)}}else if(document.attachEvent){IEContentLoaded(window,fn)}function IEContentLoaded(w,fn){var d=w.document,done=false,init=function(){if(!done){done=true;fn()}};var polling=function(){try{d.documentElement.doScroll("left")}catch(e){setTimeout(polling,50);return}init()};polling();d.onreadystatechange=function(){if(d.readyState=="complete"){d.onreadystatechange=null;init()}}}};var before=function(el,target){target.parentNode.insertBefore(el,target)};var prepend=function(el,target){if(target.firstChild){before(el,target.firstChild)}else{target.appendChild(el)}};function appendSvg(){var div,svg;div=document.createElement("div");div.innerHTML=svgSprite;svgSprite=null;svg=div.getElementsByTagName("svg")[0];if(svg){svg.setAttribute("aria-hidden","true");svg.style.position="absolute";svg.style.width=0;svg.style.height=0;svg.style.overflow="hidden";prepend(svg,document.body)}}if(shouldInjectCss&&!window.__iconfont__svg__cssinject__){window.__iconfont__svg__cssinject__=true;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(e){console&&console.log(e)}}ready(appendSvg)})(window)
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||||
|
<!--
|
||||||
|
2013-9-30: Created.
|
||||||
|
-->
|
||||||
|
<svg>
|
||||||
|
<metadata>
|
||||||
|
Created by iconfont
|
||||||
|
</metadata>
|
||||||
|
<defs>
|
||||||
|
|
||||||
|
<font id="iconfont" horiz-adv-x="1024" >
|
||||||
|
<font-face
|
||||||
|
font-family="iconfont"
|
||||||
|
font-weight="500"
|
||||||
|
font-stretch="normal"
|
||||||
|
units-per-em="1024"
|
||||||
|
ascent="896"
|
||||||
|
descent="-128"
|
||||||
|
/>
|
||||||
|
<missing-glyph />
|
||||||
|
|
||||||
|
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
|
||||||
|
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
|
||||||
|
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
|
||||||
|
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<glyph glyph-name="duigou" unicode="" d="M512 896C228.266667 896 0 667.733333 0 384c0-283.733333 228.266667-512 512-512 283.733333 0 512 228.266667 512 512C1024 667.733333 795.733333 896 512 896zM832 512 492.8 172.8C469.333333 149.333333 426.666667 149.333333 403.2 172.8L192 384c0 0-32 32 0 64s64 0 64 0l192-192 320 320c0 0 32 32 64 0S832 512 832 512z" horiz-adv-x="1024" />
|
||||||
|
|
||||||
|
|
||||||
|
<glyph glyph-name="step" unicode="" d="M511.3 831.4h-1.8c-0.7 0.4-1.6 0.7-2.5 0.7H188.3c-69 0-125.5-56.5-125.5-125.5v-289.1c-0.9-11.9-1.4-24-1.4-36.1 0-248.5 201.5-450 450-450s450 201.5 450 450-201.5 450-450.1 450z m162.1-724.8H326.5v66H462V632l-139-40.2v70.3l215.2 62.5v-552h135.2v-66z" horiz-adv-x="1024" />
|
||||||
|
|
||||||
|
|
||||||
|
<glyph glyph-name="step1" unicode="" d="M511.3 831.4h-1.8c-0.7 0.4-1.6 0.7-2.5 0.7H188.3c-69 0-125.5-56.5-125.5-125.5v-289.1c-0.9-11.9-1.4-24-1.4-36.1 0-248.5 201.5-450 450-450s450 201.5 450 450-201.5 450-450.1 450z m91.4-684.9c-39.2-33.3-91.3-50-156.4-50-57.3 0-103.5 10.7-138.7 32v79.3c41.4-32 88.1-48 140.2-48 41.7 0 74.7 10.2 99 30.7 24.3 20.4 36.5 47.9 36.5 82.2 0 76.6-54.8 114.8-164.5 114.8h-50.4v63.3h48c97.1 0 145.7 35.9 145.7 107.8 0 66.4-37.1 99.6-111.3 99.6-42.5 0-82.4-14.3-119.9-43v72.3c39.6 22.9 85.8 34.4 138.7 34.4 51.6 0 93-13.5 124.2-40.4s46.9-61.9 46.9-104.9c0-79.2-40.4-130.1-121.1-152.7v-1.6c43.8-4.7 78.3-20.1 103.7-46.3s38.1-58.8 38.1-97.9c0-54.4-19.6-98.3-58.7-131.6z" horiz-adv-x="1024" />
|
||||||
|
|
||||||
|
|
||||||
|
<glyph glyph-name="step2" unicode="" d="M511.3 831.4h-1.8c-0.7 0.4-1.6 0.7-2.5 0.7H188.3c-69 0-125.5-56.5-125.5-125.5v-289.1c-0.9-11.9-1.4-24-1.4-36.1 0-248.5 201.5-450 450-450s450 201.5 450 450-201.5 450-450.1 450z m150.8-656.8v-68h-368V173l175.8 175.4c48.4 48.4 80.9 86.8 97.3 115 16.4 28.3 24.6 57.5 24.6 87.7 0 34.4-9.6 60.7-28.9 79.1-19.3 18.4-47.1 27.5-83.6 27.5-53.9 0-105.3-22.9-154.3-68.8v77.7c47.7 36.7 103.1 55.1 166.4 55.1 54.4 0 97.4-14.7 128.9-44.1 31.5-29.4 47.3-69 47.3-118.8 0-37.5-10.1-74.3-30.3-110.4-20.2-36.1-58.4-82-114.6-137.7l-138-134.5v-1.6h277.4z" horiz-adv-x="1024" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</font>
|
||||||
|
</defs></svg>
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 187 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,7 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||||
|
@ -16,7 +17,7 @@ from .tasks import write_login_log_async
|
||||||
from .models import User, UserGroup
|
from .models import User, UserGroup
|
||||||
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
|
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
|
||||||
IsSuperUserOrAppUser
|
IsSuperUserOrAppUser
|
||||||
from .utils import check_user_valid, generate_token
|
from .utils import check_user_valid, generate_token, get_login_ip, check_otp_code
|
||||||
from common.mixins import IDInFilterMixin
|
from common.mixins import IDInFilterMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
|
||||||
|
@ -129,47 +130,110 @@ class UserToken(APIView):
|
||||||
|
|
||||||
class UserProfile(APIView):
|
class UserProfile(APIView):
|
||||||
permission_classes = (IsValidUser,)
|
permission_classes = (IsValidUser,)
|
||||||
|
serializer_class = UserSerializer
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return Response(request.user.to_json())
|
# return Response(request.user.to_json())
|
||||||
|
return Response(self.serializer_class(request.user).data)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
return Response(request.user.to_json())
|
return Response(self.serializer_class(request.user).data)
|
||||||
|
|
||||||
|
|
||||||
|
class UserOtpAuthApi(APIView):
|
||||||
|
permission_classes = (AllowAny,)
|
||||||
|
serializer_class = UserSerializer
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
otp_code = request.data.get('otp_code', '')
|
||||||
|
seed = request.data.get('seed', '')
|
||||||
|
|
||||||
|
user = cache.get(seed, None)
|
||||||
|
if not user:
|
||||||
|
return Response({'msg': '请先进行用户名和密码验证'}, status=401)
|
||||||
|
|
||||||
|
if not check_otp_code(user.otp_secret_key, otp_code):
|
||||||
|
return Response({'msg': 'otp认证失败'}, status=401)
|
||||||
|
|
||||||
|
token = generate_token(request, user)
|
||||||
|
self.write_login_log(request, user)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
'token': token,
|
||||||
|
'user': self.serializer_class(user).data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def write_login_log(request, user):
|
||||||
|
login_ip = request.data.get('remote_addr', None)
|
||||||
|
login_type = request.data.get('login_type', '')
|
||||||
|
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||||
|
|
||||||
|
if not login_ip:
|
||||||
|
login_ip = get_login_ip(request)
|
||||||
|
|
||||||
|
write_login_log_async.delay(
|
||||||
|
user.username, ip=login_ip,
|
||||||
|
type=login_type, user_agent=user_agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserAuthApi(APIView):
|
class UserAuthApi(APIView):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
|
serializer_class = UserSerializer
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
user, msg = self.check_user_valid(request)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return Response({'msg': msg}, status=401)
|
||||||
|
|
||||||
|
if not user.otp_enabled:
|
||||||
|
token = generate_token(request, user)
|
||||||
|
self.write_login_log(request, user)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
'token': token,
|
||||||
|
'user': self.serializer_class(user).data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
seed = uuid.uuid4().hex
|
||||||
|
cache.set(seed, user, 300)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
'code': 101,
|
||||||
|
'msg': '请携带seed值,进行OTP二次认证',
|
||||||
|
'otp_url': reverse('api-users:user-otp-auth'),
|
||||||
|
'seed': seed,
|
||||||
|
'user': self.serializer_class(user).data
|
||||||
|
}, status=300)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_user_valid(request):
|
||||||
username = request.data.get('username', '')
|
username = request.data.get('username', '')
|
||||||
password = request.data.get('password', '')
|
password = request.data.get('password', '')
|
||||||
public_key = request.data.get('public_key', '')
|
public_key = request.data.get('public_key', '')
|
||||||
login_type = request.data.get('login_type', '')
|
|
||||||
login_ip = request.data.get('remote_addr', None)
|
|
||||||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
|
||||||
|
|
||||||
if not login_ip:
|
|
||||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
|
||||||
|
|
||||||
if x_forwarded_for and x_forwarded_for[0]:
|
|
||||||
login_ip = x_forwarded_for[0]
|
|
||||||
else:
|
|
||||||
login_ip = request.META.get("REMOTE_ADDR")
|
|
||||||
|
|
||||||
user, msg = check_user_valid(
|
user, msg = check_user_valid(
|
||||||
username=username, password=password,
|
username=username, password=password,
|
||||||
public_key=public_key
|
public_key=public_key
|
||||||
)
|
)
|
||||||
|
return user, msg
|
||||||
|
|
||||||
if user:
|
@staticmethod
|
||||||
token = generate_token(request, user)
|
def write_login_log(request, user):
|
||||||
write_login_log_async.delay(
|
login_ip = request.data.get('remote_addr', None)
|
||||||
user.username, ip=login_ip,
|
login_type = request.data.get('login_type', '')
|
||||||
type=login_type, user_agent=user_agent,
|
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||||
)
|
|
||||||
return Response({'token': token, 'user': user.to_json()})
|
if not login_ip:
|
||||||
else:
|
login_ip = get_login_ip(request)
|
||||||
return Response({'msg': msg}, status=401)
|
|
||||||
|
write_login_log_async.delay(
|
||||||
|
user.username, ip=login_ip,
|
||||||
|
type=login_type, user_agent=user_agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserConnectionTokenApi(APIView):
|
class UserConnectionTokenApi(APIView):
|
||||||
|
|
|
@ -18,6 +18,18 @@ class UserLoginForm(AuthenticationForm):
|
||||||
captcha = CaptchaField()
|
captcha = CaptchaField()
|
||||||
|
|
||||||
|
|
||||||
|
class UserCheckPasswordForm(forms.Form):
|
||||||
|
username = forms.CharField(label=_('Username'), max_length=100)
|
||||||
|
password = forms.CharField(
|
||||||
|
label=_('Password'), widget=forms.PasswordInput,
|
||||||
|
max_length=128, strip=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserCheckOtpCodeForm(forms.Form):
|
||||||
|
otp_code = forms.CharField(label=_('Otp_code'), max_length=6)
|
||||||
|
|
||||||
|
|
||||||
class UserCreateUpdateForm(forms.ModelForm):
|
class UserCreateUpdateForm(forms.ModelForm):
|
||||||
role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP)
|
role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP)
|
||||||
password = forms.CharField(
|
password = forms.CharField(
|
||||||
|
|
|
@ -219,15 +219,20 @@ class User(AbstractUser):
|
||||||
def otp_enabled(self):
|
def otp_enabled(self):
|
||||||
return self.otp_level > 0
|
return self.otp_level > 0
|
||||||
|
|
||||||
def enabled_otp(self):
|
@property
|
||||||
self.otp_level = 1
|
def otp_force_enabled(self):
|
||||||
|
return self.otp_level == 2
|
||||||
|
|
||||||
|
def enable_otp(self):
|
||||||
|
if not self.otp_force_enabled:
|
||||||
|
self.otp_level = 1
|
||||||
|
|
||||||
def force_enable_otp(self):
|
def force_enable_otp(self):
|
||||||
self.otp_level = 2
|
self.otp_level = 2
|
||||||
|
|
||||||
@property
|
def disable_otp(self):
|
||||||
def otp_force_enabled(self):
|
self.otp_level = 0
|
||||||
return self.otp_level == 2
|
self.otp_secret_key = None
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
return OrderedDict({
|
return OrderedDict({
|
||||||
|
@ -241,6 +246,7 @@ class User(AbstractUser):
|
||||||
'groups': [group.name for group in self.groups.all()],
|
'groups': [group.name for group in self.groups.all()],
|
||||||
'wechat': self.wechat,
|
'wechat': self.wechat,
|
||||||
'phone': self.phone,
|
'phone': self.phone,
|
||||||
|
'otp_level': self.otp_level,
|
||||||
'comment': self.comment,
|
'comment': self.comment,
|
||||||
'date_expired': self.date_expired.strftime('%Y-%m-%d %H:%M:%S') if self.date_expired is not None else None
|
'date_expired': self.date_expired.strftime('%Y-%m-%d %H:%M:%S') if self.date_expired is not None else None
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,7 +19,10 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
list_serializer_class = BulkListSerializer
|
list_serializer_class = BulkListSerializer
|
||||||
exclude = ['first_name', 'last_name', 'password', '_private_key', '_public_key']
|
exclude = [
|
||||||
|
'first_name', 'last_name', 'password', '_private_key',
|
||||||
|
'_public_key', '_otp_secret_key', 'user_permissions'
|
||||||
|
]
|
||||||
|
|
||||||
def get_field_names(self, declared_fields, info):
|
def get_field_names(self, declared_fields, info):
|
||||||
fields = super(UserSerializer, self).get_field_names(declared_fields, info)
|
fields = super(UserSerializer, self).get_field_names(declared_fields, info)
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title> Jumpserver </title>
|
||||||
|
<link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="{% static 'fonts/font_otp/iconfont.css' %}" />
|
||||||
|
<link rel="stylesheet" href="{% static 'css/otp.css' %}" />
|
||||||
|
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
||||||
|
<script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--头部-->
|
||||||
|
<header>
|
||||||
|
<div class="logo">
|
||||||
|
<a href="{% url 'index' %}">
|
||||||
|
<img src="{% static 'img/logo.png' %}" alt="" width="50px" height="50px"/>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'index' %}">Jumpserver</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'index' %}">首页</a>
|
||||||
|
<b>丨</b>
|
||||||
|
<a href="http://docs.jumpserver.org/zh/docs/">文档</a>
|
||||||
|
<b>丨</b>
|
||||||
|
<a href="https://www.github.com/jumpserver/">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!--内容-->
|
||||||
|
<article>
|
||||||
|
<div class="clearfix">
|
||||||
|
<ul class="change-color">
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<i class="iconfont icon-step active"></i>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<div class="back">验证身份</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<i class="iconfont icon-step2"></i>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<div class="back">安装应用</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<i class="iconfont icon-step1"></i>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<div class="back">绑定TOTP</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<i class="iconfont icon-duigou"></i>
|
||||||
|
</div>
|
||||||
|
<div>完成</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div >
|
||||||
|
<div class="verify">安全令牌验证 账户 <span>{{ user.username }}</span> 请按照以下步骤完成绑定操作</div>
|
||||||
|
<div class="line"></div>
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
|
||||||
|
<div class="" style="margin-top: 100px;">
|
||||||
|
{% include '_copyright.html' %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title> Jumpserver </title>
|
||||||
|
<link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
|
||||||
|
{% include '_head_css_js.html' %}
|
||||||
|
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
||||||
|
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||||
|
<script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script>
|
||||||
|
<style>
|
||||||
|
.captcha {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="gray-bg">
|
||||||
|
<div class="loginColumns animated fadeInDown">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2 class="font-bold">欢迎使用Jumpserver开源堡垒机</h2>
|
||||||
|
<p>
|
||||||
|
全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
改变世界,从一点点开始。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="ibox-content">
|
||||||
|
<div>
|
||||||
|
<img src="{% static 'img/logo.png' %}" width="60" height="60">
|
||||||
|
<span class="font-bold text-center" style="font-size: 24px; font-family: inherit; margin-left: 20px">{% trans '二次认证' %}</span>
|
||||||
|
</div>
|
||||||
|
<div class="m-t">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<p style="margin:30px auto;" class="text-center"><strong style="color:#000000">账号保护已开启,请根据提示完成以下操作</strong></p>
|
||||||
|
<div class="text-center">
|
||||||
|
<img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117">
|
||||||
|
</div>
|
||||||
|
<p style="margin: 30px auto">请在手机中打开Google Authenticator应用,输入6位动态码</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="m-t" role="form" method="post" action="">
|
||||||
|
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if 'otp_code' in form.errors %}
|
||||||
|
<p class="red-fonts">{{ form.otp_code.errors.as_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" class="form-control" name="otp_code" placeholder="{% trans 'Six figures' %}" required="">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Next' %}</button>
|
||||||
|
|
||||||
|
<a href="#">
|
||||||
|
<small>{% trans "Can't provide otp code? Please contact the administrator" %}</small>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<p class="m-t">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{% include '_copyright.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -87,10 +87,18 @@
|
||||||
<td>{% trans 'Role' %}:</td>
|
<td>{% trans 'Role' %}:</td>
|
||||||
<td><b>{{ user_object.get_role_display }}</b></td>
|
<td><b>{{ user_object.get_role_display }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
{# <tr>#}
|
<tr>
|
||||||
{# <td>{% trans 'Enable OTP' %}:</td>#}
|
<td>{% trans 'Enable OTP' %}:</td>
|
||||||
{# <td><b>{{ user_object.enable_otp|yesno:"Yes,No,Unknown"}}</b></td>#}
|
<td><b>
|
||||||
{# </tr>#}
|
{% if user_object.otp_force_enabled %}
|
||||||
|
{% trans 'Force enabled' %}
|
||||||
|
{% elif user_object.otp_enabled%}
|
||||||
|
{% trans 'Enabled' %}
|
||||||
|
{% else %}
|
||||||
|
{% trans 'Disabled' %}
|
||||||
|
{% endif %}
|
||||||
|
</b></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Date expired' %}:</td>
|
<td>{% trans 'Date expired' %}:</td>
|
||||||
<td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td>
|
<td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td>
|
||||||
|
@ -137,22 +145,23 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</span></td>
|
</span></td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Force enabled OTP' %}:</td>
|
||||||
|
<td><span class="pull-right">
|
||||||
|
<div class="switch">
|
||||||
|
<div class="onoffswitch">
|
||||||
|
<input type="checkbox" class="onoffswitch-checkbox" {% if user_object.otp_force_enabled%} checked {% endif %}{% if request.user == user_object %} disabled {% endif %}
|
||||||
|
id="force_enable_otp">
|
||||||
|
<label class="onoffswitch-label" for="force_enable_otp">
|
||||||
|
<span class="onoffswitch-inner"></span>
|
||||||
|
<span class="onoffswitch-switch"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
{# <tr>#}
|
|
||||||
{# <td>{% trans 'Enable OTP' %}:</td>#}
|
|
||||||
{# <td><span class="pull-right">#}
|
|
||||||
{# <div class="switch">#}
|
|
||||||
{# <div class="onoffswitch">#}
|
|
||||||
{# <input type="checkbox" class="onoffswitch-checkbox" {% if user_object.enable_otp %} checked {% endif %}#}
|
|
||||||
{# id="enable_otp">#}
|
|
||||||
{# <label class="onoffswitch-label" for="enable_otp">#}
|
|
||||||
{# <span class="onoffswitch-inner"></span>#}
|
|
||||||
{# <span class="onoffswitch-switch"></span>#}
|
|
||||||
{# </label>#}
|
|
||||||
{# </div>#}
|
|
||||||
{# </div>#}
|
|
||||||
{# </span></td>#}
|
|
||||||
{# </tr>#}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Send reset password mail' %}:</td>
|
<td>{% trans 'Send reset password mail' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -277,19 +286,28 @@ $(document).ready(function() {
|
||||||
success_message: success
|
success_message: success
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
{#.on('click', '#enable_otp', function() {#}
|
.on('click', '#force_enable_otp', function() {
|
||||||
{# var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";#}
|
var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";
|
||||||
{# var checked = $(this).prop('checked');#}
|
var checked = $(this).prop('checked');
|
||||||
{# var body = {#}
|
var otp_level;
|
||||||
{# 'enable_otp': checked#}
|
var otp_secret_key;
|
||||||
{# };#}
|
if(checked){
|
||||||
{# var success = '{% trans "Update successfully!" %}';#}
|
otp_level = 2
|
||||||
{# APIUpdateAttr({#}
|
}else{
|
||||||
{# url: the_url,#}
|
otp_level = 0;
|
||||||
{# body: JSON.stringify(body),#}
|
otp_secret_key = '';
|
||||||
{# success_message: success#}
|
}
|
||||||
{# });#}
|
var body = {
|
||||||
{# });#}
|
'otp_level': otp_level,
|
||||||
|
'otp_secret_key': otp_secret_key
|
||||||
|
};
|
||||||
|
var success = '{% trans "Update successfully!" %}';
|
||||||
|
APIUpdateAttr({
|
||||||
|
url: the_url,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
success_message: success
|
||||||
|
});
|
||||||
|
})
|
||||||
.on('click', '#btn_join_group', function() {
|
.on('click', '#btn_join_group', function() {
|
||||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends 'users/_base_otp.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="verify">
|
||||||
|
<p style="margin: 20px auto;"><strong style="color: #000000">账号保护已开启,请根据提示完成以下操作</strong></p>
|
||||||
|
<img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117">
|
||||||
|
<p style="margin: 20px auto;">请在手机中打开Google Authenticator应用,输入6为动态码</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="" role="form" method="post" action="">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if 'otp_code' in form.errors %}
|
||||||
|
<p class="red-fonts">{{ form.otp_code.errors.as_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="form-input">
|
||||||
|
<input type="text" class="" name="otp_code" placeholder="{% trans 'Six figures' %}" required="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="next">{% trans 'Next' %}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('.change-color li').eq(2).remove();
|
||||||
|
$('.change-color li:eq(1) div').eq(1).html('解绑MFA')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
{% extends 'users/_base_otp.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="verify">
|
||||||
|
<p style="margin:20px auto;"><strong style="color: #000000">使用手机 Google Authenticator 应用扫描以下二维码,获取6位验证码</strong></p>
|
||||||
|
|
||||||
|
<div id="qr_code"></div>
|
||||||
|
|
||||||
|
<form class="" role="form" method="post" action="">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form-input">
|
||||||
|
<input type="text" class="" name="otp_code" placeholder="{% trans 'Six figures' %}" required="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="next">{% trans 'Next' %}</button>
|
||||||
|
|
||||||
|
|
||||||
|
{% if 'otp_code' in form.errors %}
|
||||||
|
<p style="color: #ed5565">{{ form.otp_code.errors.as_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$('.change-color li:eq(1) i').css('color', '#1ab394');
|
||||||
|
$('.change-color li:eq(2) i').css('color', '#1ab394');
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// 生成用户绑定otp的二维码
|
||||||
|
var qrcode = new QRCode(document.getElementById('qr_code'), {
|
||||||
|
text: "{{ otp_uri|safe}}",
|
||||||
|
width: 180 ,
|
||||||
|
height: 180,
|
||||||
|
colorDark: '#000000',
|
||||||
|
colorLight: '#ffffff',
|
||||||
|
correctlevel: QRCode.CorrectLevel.H
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('qr_code').removeAttribute("title");
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends 'users/_base_otp.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="verify">
|
||||||
|
<p style="margin: 20px auto;"><strong style="color: #000000">请在手机端下载并安装 Google Authenticator 应用</strong></p>
|
||||||
|
<div>
|
||||||
|
<img src="{% static 'img/authenticator_android.png' %}" width="128" height="128" alt="">
|
||||||
|
<p>Android手机下载</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<img src="{% static 'img/authenticator_iphone.png' %}" width="128" height="128" alt="">
|
||||||
|
<p>iPhone手机下载</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="margin: 20px auto;"></p>
|
||||||
|
<p style="margin: 20px auto;"><strong style="color: #000000">安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步)</strong></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{% url 'users:user-otp-enable-bind' %}" class="next">{% trans 'Next' %}</a>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('.change-color li:eq(1) i').css('color', '#1ab394')
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends 'users/_base_otp.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form class="" role="form" method="post" action="">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form-input">
|
||||||
|
<input type="text" class="" name="{{ form.username.html_name }}" value="{{ form.username.value }}" readonly="readonly" required="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-input">
|
||||||
|
<input type="password" class="" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="next">{% trans 'Next' %}</button>
|
||||||
|
|
||||||
|
{% if 'password' in form.errors %}
|
||||||
|
<p class="red-fonts">{{ form.password.errors.as_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -65,7 +65,15 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-navy">{% trans 'OTP' %}</td>
|
<td class="text-navy">{% trans 'OTP' %}</td>
|
||||||
<td>{{ user.otp_enabled|yesno:"Yes,No,Unkown" }}</td>
|
<td>
|
||||||
|
{% if user.otp_force_enabled %}
|
||||||
|
{% trans 'Force enable' %}
|
||||||
|
{% elif user.otp_enabled%}
|
||||||
|
{% trans 'Enable' %}
|
||||||
|
{% else %}
|
||||||
|
{% trans 'Disable' %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-navy">{% trans 'Public key' %}</td>
|
<td class="text-navy">{% trans 'Public key' %}</td>
|
||||||
|
@ -136,6 +144,27 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr class="no-borders-tr">
|
||||||
|
<td>{% trans 'Update otp' %}:</td>
|
||||||
|
<td>
|
||||||
|
<span class="pull-right">
|
||||||
|
<a type="button" class="btn btn-primary btn-xs" style="width: 54px" id=""
|
||||||
|
href="
|
||||||
|
{% if request.user.otp_enabled and request.user.otp_secret_key %}
|
||||||
|
{% if request.user.otp_force_enabled %}
|
||||||
|
" disabled >{% trans 'Disable' %}
|
||||||
|
{% else %}
|
||||||
|
{% url 'users:user-otp-disable-authentication' %}
|
||||||
|
">{% trans 'Disable' %}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% url 'users:user-otp-enable-authentication' %}
|
||||||
|
">{% trans 'Enable' %}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Update SSH public key' %}:</td>
|
<td>{% trans 'Update SSH public key' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -20,6 +20,7 @@ urlpatterns = [
|
||||||
url(r'^v1/connection-token/$', api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
url(r'^v1/connection-token/$', api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
||||||
url(r'^v1/profile/$', api.UserProfile.as_view(), name='user-profile'),
|
url(r'^v1/profile/$', api.UserProfile.as_view(), name='user-profile'),
|
||||||
url(r'^v1/auth/$', api.UserAuthApi.as_view(), name='user-auth'),
|
url(r'^v1/auth/$', api.UserAuthApi.as_view(), name='user-auth'),
|
||||||
|
url(r'^v1/otp/auth/$', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
||||||
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/$',
|
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/$',
|
||||||
api.ChangeUserPasswordApi.as_view(), name='change-user-password'),
|
api.ChangeUserPasswordApi.as_view(), name='change-user-password'),
|
||||||
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/reset/$',
|
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/reset/$',
|
||||||
|
|
|
@ -10,6 +10,7 @@ urlpatterns = [
|
||||||
# Login view
|
# Login view
|
||||||
url(r'^login$', views.UserLoginView.as_view(), name='login'),
|
url(r'^login$', views.UserLoginView.as_view(), name='login'),
|
||||||
url(r'^logout$', views.UserLogoutView.as_view(), name='logout'),
|
url(r'^logout$', views.UserLogoutView.as_view(), name='logout'),
|
||||||
|
url(r'^login/otp$', views.UserLoginOtpView.as_view(), name='login-otp'),
|
||||||
url(r'^password/forgot$', views.UserForgotPasswordView.as_view(), name='forgot-password'),
|
url(r'^password/forgot$', views.UserForgotPasswordView.as_view(), name='forgot-password'),
|
||||||
url(r'^password/forgot/sendmail-success$', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
|
url(r'^password/forgot/sendmail-success$', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
|
||||||
url(r'^password/reset$', views.UserResetPasswordView.as_view(), name='reset-password'),
|
url(r'^password/reset$', views.UserResetPasswordView.as_view(), name='reset-password'),
|
||||||
|
@ -21,6 +22,11 @@ urlpatterns = [
|
||||||
url(r'^profile/password/update/$', views.UserPasswordUpdateView.as_view(), name='user-password-update'),
|
url(r'^profile/password/update/$', views.UserPasswordUpdateView.as_view(), name='user-password-update'),
|
||||||
url(r'^profile/pubkey/update/$', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'),
|
url(r'^profile/pubkey/update/$', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'),
|
||||||
url(r'^profile/pubkey/generate/$', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'),
|
url(r'^profile/pubkey/generate/$', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'),
|
||||||
|
url(r'^profile/otp/enable/authentication/$', views.UserOtpEnableAuthenticationView.as_view(), name='user-otp-enable-authentication'),
|
||||||
|
url(r'^profile/otp/enable/install-app/$', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'),
|
||||||
|
url(r'^profile/otp/enable/bind/$', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'),
|
||||||
|
url(r'^profile/otp/disable/authentication/$', views.UserOtpDisableAuthenticationView.as_view(), name='user-otp-disable-authentication'),
|
||||||
|
url(r'^profile/otp/settings-success/$', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'),
|
||||||
|
|
||||||
# User view
|
# User view
|
||||||
url(r'^user$', views.UserListView.as_view(), name='user-list'),
|
url(r'^user$', views.UserListView.as_view(), name='user-list'),
|
||||||
|
@ -34,7 +40,6 @@ urlpatterns = [
|
||||||
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
|
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
|
||||||
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/login-history', views.UserDetailView.as_view(), name='user-login-history'),
|
url(r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/login-history', views.UserDetailView.as_view(), name='user-login-history'),
|
||||||
|
|
||||||
|
|
||||||
# User group view
|
# User group view
|
||||||
url(r'^user-group$', views.UserGroupListView.as_view(), name='user-group-list'),
|
url(r'^user-group$', views.UserGroupListView.as_view(), name='user-group-list'),
|
||||||
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})$', views.UserGroupDetailView.as_view(), name='user-group-detail'),
|
url(r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})$', views.UserGroupDetailView.as_view(), name='user-group-detail'),
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
#
|
#
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import os
|
||||||
|
import pyotp
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
@ -17,7 +19,6 @@ from common.tasks import send_mail_async
|
||||||
from common.utils import reverse, get_object_or_none
|
from common.utils import reverse, get_object_or_none
|
||||||
from .models import User, LoginLog
|
from .models import User, LoginLog
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('jumpserver')
|
logger = logging.getLogger('jumpserver')
|
||||||
|
|
||||||
|
|
||||||
|
@ -163,7 +164,7 @@ def generate_token(request, user):
|
||||||
remote_addr = request.META.get('REMOTE_ADDR', '')
|
remote_addr = request.META.get('REMOTE_ADDR', '')
|
||||||
if not isinstance(remote_addr, bytes):
|
if not isinstance(remote_addr, bytes):
|
||||||
remote_addr = remote_addr.encode("utf-8")
|
remote_addr = remote_addr.encode("utf-8")
|
||||||
remote_addr = base64.b16encode(remote_addr) #.replace(b'=', '')
|
remote_addr = base64.b16encode(remote_addr) # .replace(b'=', '')
|
||||||
token = cache.get('%s_%s' % (user.id, remote_addr))
|
token = cache.get('%s_%s' % (user.id, remote_addr))
|
||||||
if not token:
|
if not token:
|
||||||
token = uuid.uuid4().hex
|
token = uuid.uuid4().hex
|
||||||
|
@ -181,6 +182,16 @@ def validate_ip(ip):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_login_ip(request):
|
||||||
|
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||||
|
if x_forwarded_for and x_forwarded_for[0]:
|
||||||
|
login_ip = x_forwarded_for[0]
|
||||||
|
else:
|
||||||
|
login_ip = request.META.get('REMOTE_ADDR', '')
|
||||||
|
|
||||||
|
return login_ip
|
||||||
|
|
||||||
|
|
||||||
def write_login_log(username, type='', ip='', user_agent=''):
|
def write_login_log(username, type='', ip='', user_agent=''):
|
||||||
if not (ip and validate_ip(ip)):
|
if not (ip and validate_ip(ip)):
|
||||||
ip = ip[:15]
|
ip = ip[:15]
|
||||||
|
@ -211,3 +222,39 @@ def get_ip_city(ip, timeout=10):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return city
|
return city
|
||||||
|
|
||||||
|
|
||||||
|
def get_tmp_user_from_session(request):
|
||||||
|
user_id = request.session.get('tmp_user_id')
|
||||||
|
user = get_object_or_none(User, pk=user_id)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def set_tmp_user_to_session(request, user):
|
||||||
|
request.session['tmp_user_id'] = str(user.id)
|
||||||
|
|
||||||
|
|
||||||
|
def redirect_user_first_login_or_index(request, redirect_field_name):
|
||||||
|
if request.user.is_first_login:
|
||||||
|
return reverse('users:user-first-login')
|
||||||
|
return request.POST.get(
|
||||||
|
redirect_field_name,
|
||||||
|
request.GET.get(redirect_field_name, reverse('index')))
|
||||||
|
|
||||||
|
|
||||||
|
def generate_otp_uri(request, issuer="Jumpserver"):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
user = request.user
|
||||||
|
else:
|
||||||
|
user = get_tmp_user_from_session(request)
|
||||||
|
otp_secret_key = cache.get(request.session.session_key+'otp_key', '')
|
||||||
|
if not otp_secret_key:
|
||||||
|
otp_secret_key = base64.b32encode(os.urandom(10)).decode('utf-8')
|
||||||
|
cache.set(request.session.session_key+'otp_key', otp_secret_key, 600)
|
||||||
|
totp = pyotp.TOTP(otp_secret_key)
|
||||||
|
return totp.provisioning_uri(name=user.username, issuer_name=issuer)
|
||||||
|
|
||||||
|
|
||||||
|
def check_otp_code(otp_secret_key, otp_code):
|
||||||
|
totp = pyotp.TOTP(otp_secret_key)
|
||||||
|
return totp.verify(otp_code)
|
||||||
|
|
|
@ -23,13 +23,14 @@ from django.conf import settings
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
||||||
from ..models import User, LoginLog
|
from ..models import User, LoginLog
|
||||||
from ..utils import send_reset_password_mail
|
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, redirect_user_first_login_or_index, \
|
||||||
|
get_tmp_user_from_session, set_tmp_user_to_session
|
||||||
from ..tasks import write_login_log_async
|
from ..tasks import write_login_log_async
|
||||||
from .. import forms
|
from .. import forms
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserLoginView', 'UserLogoutView',
|
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
|
||||||
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
|
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
|
||||||
'UserResetPasswordView', 'UserResetPasswordSuccessView',
|
'UserResetPasswordView', 'UserResetPasswordSuccessView',
|
||||||
'UserFirstLoginView', 'LoginLogListView'
|
'UserFirstLoginView', 'LoginLogListView'
|
||||||
|
@ -53,27 +54,24 @@ class UserLoginView(FormView):
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if not self.request.session.test_cookie_worked():
|
if not self.request.session.test_cookie_worked():
|
||||||
return HttpResponse(_("Please enable cookies and try again."))
|
return HttpResponse(_("Please enable cookies and try again."))
|
||||||
auth_login(self.request, form.get_user())
|
|
||||||
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
|
||||||
|
|
||||||
if x_forwarded_for and x_forwarded_for[0]:
|
set_tmp_user_to_session(self.request, form.get_user())
|
||||||
login_ip = x_forwarded_for[0]
|
|
||||||
else:
|
|
||||||
login_ip = self.request.META.get('REMOTE_ADDR', '')
|
|
||||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
|
||||||
write_login_log_async.delay(
|
|
||||||
self.request.user.username, type='W',
|
|
||||||
ip=login_ip, user_agent=user_agent
|
|
||||||
)
|
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
if self.request.user.is_first_login:
|
user = get_tmp_user_from_session(self.request)
|
||||||
return reverse('users:user-first-login')
|
|
||||||
|
|
||||||
return self.request.POST.get(
|
if user.otp_enabled and user.otp_secret_key:
|
||||||
self.redirect_field_name,
|
# 1,2 & T
|
||||||
self.request.GET.get(self.redirect_field_name, reverse('index')))
|
return reverse('users:login-otp')
|
||||||
|
elif user.otp_enabled and not user.otp_secret_key:
|
||||||
|
# 1,2 & F
|
||||||
|
return reverse('users:user-otp-enable-authentication')
|
||||||
|
elif not user.otp_enabled:
|
||||||
|
# 0 & T,F
|
||||||
|
auth_login(self.request, user)
|
||||||
|
self.write_login_log()
|
||||||
|
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
|
@ -82,6 +80,44 @@ class UserLoginView(FormView):
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def write_login_log(self):
|
||||||
|
login_ip = get_login_ip(self.request)
|
||||||
|
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||||
|
write_login_log_async.delay(
|
||||||
|
self.request.user.username, type='W',
|
||||||
|
ip=login_ip, user_agent=user_agent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserLoginOtpView(FormView):
|
||||||
|
template_name = 'users/login_otp.html'
|
||||||
|
form_class = forms.UserCheckOtpCodeForm
|
||||||
|
redirect_field_name = 'next'
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
user = get_tmp_user_from_session(self.request)
|
||||||
|
otp_code = form.cleaned_data.get('otp_code')
|
||||||
|
otp_secret_key = user.otp_secret_key
|
||||||
|
|
||||||
|
if check_otp_code(otp_secret_key, otp_code):
|
||||||
|
auth_login(self.request, user)
|
||||||
|
self.write_login_log()
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
else:
|
||||||
|
form.add_error('otp_code', _('Otp code invalid'))
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
||||||
|
|
||||||
|
def write_login_log(self):
|
||||||
|
login_ip = get_login_ip(self.request)
|
||||||
|
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||||
|
write_login_log_async.delay(
|
||||||
|
self.request.user.username, type='W',
|
||||||
|
ip=login_ip, user_agent=user_agent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(never_cache, name='dispatch')
|
@method_decorator(never_cache, name='dispatch')
|
||||||
class UserLogoutView(TemplateView):
|
class UserLogoutView(TemplateView):
|
||||||
|
|
|
@ -11,6 +11,7 @@ from io import StringIO
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.contrib.auth import authenticate, login as auth_login
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
|
@ -34,9 +35,9 @@ from common.mixins import JSONResponseMixin
|
||||||
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
|
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
|
||||||
from .. import forms
|
from .. import forms
|
||||||
from ..models import User, UserGroup
|
from ..models import User, UserGroup
|
||||||
from ..utils import AdminUserRequiredMixin
|
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, get_tmp_user_from_session
|
||||||
from ..signals import post_user_create
|
from ..signals import post_user_create
|
||||||
|
from ..tasks import write_login_log_async
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserListView', 'UserCreateView', 'UserDetailView',
|
'UserListView', 'UserCreateView', 'UserDetailView',
|
||||||
|
@ -46,6 +47,9 @@ __all__ = [
|
||||||
'UserProfileUpdateView', 'UserPasswordUpdateView',
|
'UserProfileUpdateView', 'UserPasswordUpdateView',
|
||||||
'UserPublicKeyUpdateView', 'UserBulkUpdateView',
|
'UserPublicKeyUpdateView', 'UserBulkUpdateView',
|
||||||
'UserPublicKeyGenerateView',
|
'UserPublicKeyGenerateView',
|
||||||
|
'UserOtpEnableAuthenticationView', 'UserOtpEnableInstallAppView',
|
||||||
|
'UserOtpEnableBindView', 'UserOtpSettingsSuccessView',
|
||||||
|
'UserOtpDisableAuthenticationView',
|
||||||
]
|
]
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -380,6 +384,7 @@ class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
|
||||||
|
|
||||||
class UserPublicKeyGenerateView(LoginRequiredMixin, View):
|
class UserPublicKeyGenerateView(LoginRequiredMixin, View):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
private, public = ssh_key_gen(username=request.user.username, hostname='jumpserver')
|
private, public = ssh_key_gen(username=request.user.username, hostname='jumpserver')
|
||||||
request.user.public_key = public
|
request.user.public_key = public
|
||||||
|
@ -389,3 +394,148 @@ class UserPublicKeyGenerateView(LoginRequiredMixin, View):
|
||||||
response['Content-Disposition'] = 'attachment; filename={}'.format(filename)
|
response['Content-Disposition'] = 'attachment; filename={}'.format(filename)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class UserOtpEnableAuthenticationView(FormView):
|
||||||
|
template_name = 'users/user_password_authentication.html'
|
||||||
|
form_class = forms.UserCheckPasswordForm
|
||||||
|
|
||||||
|
def get_form(self, form_class=None):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
user = self.request.user
|
||||||
|
else:
|
||||||
|
user = get_tmp_user_from_session(self.request)
|
||||||
|
form = super().get_form(form_class=form_class)
|
||||||
|
form['username'].initial = user.username
|
||||||
|
return form
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
user = self.request.user
|
||||||
|
else:
|
||||||
|
user = get_tmp_user_from_session(self.request)
|
||||||
|
context = {
|
||||||
|
'user': user
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
user = self.request.user
|
||||||
|
else:
|
||||||
|
user = get_tmp_user_from_session(self.request)
|
||||||
|
password = form.cleaned_data.get('password')
|
||||||
|
user = authenticate(username=user.username, password=password)
|
||||||
|
if not user:
|
||||||
|
form.add_error("password", _("Password invalid"))
|
||||||
|
return self.form_invalid(form)
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('users:user-otp-enable-install-app')
|
||||||
|
|
||||||
|
|
||||||
|
class UserOtpEnableInstallAppView(TemplateView):
|
||||||
|
template_name = 'users/user_otp_enable_install_app.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
user = self.request.user
|
||||||
|
else:
|
||||||
|
user = get_tmp_user_from_session(self.request)
|
||||||
|
context = {
|
||||||
|
'user': user
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class UserOtpEnableBindView(TemplateView, FormView):
|
||||||
|
template_name = 'users/user_otp_enable_bind.html'
|
||||||
|
form_class = forms.UserCheckOtpCodeForm
|
||||||
|
success_url = reverse_lazy('users:user-otp-settings-success')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
user = self.request.user
|
||||||
|
else:
|
||||||
|
user = get_tmp_user_from_session(self.request)
|
||||||
|
context = {
|
||||||
|
'otp_uri': generate_otp_uri(self.request),
|
||||||
|
'user': user
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
otp_code = form.cleaned_data.get('otp_code')
|
||||||
|
otp_secret_key = cache.get(self.request.session.session_key+'otp_key', '')
|
||||||
|
|
||||||
|
if check_otp_code(otp_secret_key, otp_code):
|
||||||
|
self.save_otp(otp_secret_key)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
else:
|
||||||
|
form.add_error("otp_code", _("Otp code invalid"))
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
def save_otp(self, otp_secret_key):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
user = self.request.user
|
||||||
|
else:
|
||||||
|
user = get_tmp_user_from_session(self.request)
|
||||||
|
user.enable_otp()
|
||||||
|
user.otp_secret_key = otp_secret_key
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
|
class UserOtpDisableAuthenticationView(FormView):
|
||||||
|
template_name = 'users/user_otp_authentication.html'
|
||||||
|
form_class = forms.UserCheckOtpCodeForm
|
||||||
|
success_url = reverse_lazy('users:user-otp-settings-success')
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
user = self.request.user
|
||||||
|
otp_code = form.cleaned_data.get('otp_code')
|
||||||
|
otp_secret_key = user.otp_secret_key
|
||||||
|
|
||||||
|
if check_otp_code(otp_secret_key, otp_code):
|
||||||
|
user.disable_otp()
|
||||||
|
user.save()
|
||||||
|
return super().form_valid(form)
|
||||||
|
else:
|
||||||
|
form.add_error('otp_code', _('Otp code invalid'))
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class UserOtpSettingsSuccessView(TemplateView):
|
||||||
|
template_name = 'flash_message_standalone.html'
|
||||||
|
|
||||||
|
# def get(self, request, *args, **kwargs):
|
||||||
|
# return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
title, describe = self.get_title_describe()
|
||||||
|
context = {
|
||||||
|
'title': title,
|
||||||
|
'messages': describe,
|
||||||
|
'interval': 1,
|
||||||
|
'redirect_url': reverse('users:login'),
|
||||||
|
'auto_redirect': True,
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def get_title_describe(self):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
user = self.request.user
|
||||||
|
auth_logout(self.request)
|
||||||
|
else:
|
||||||
|
user = get_tmp_user_from_session(self.request)
|
||||||
|
title = _('OTP enable success')
|
||||||
|
describe = _('OTP enable success, return login page')
|
||||||
|
if not user.otp_enabled:
|
||||||
|
title = _('OTP disable success')
|
||||||
|
describe = _('OTP disable success, return login page')
|
||||||
|
|
||||||
|
return title, describe
|
||||||
|
|
|
@ -54,6 +54,7 @@ pyasn1==0.4.2
|
||||||
pycparser==2.18
|
pycparser==2.18
|
||||||
pycrypto==2.6.1
|
pycrypto==2.6.1
|
||||||
pyldap==2.4.45
|
pyldap==2.4.45
|
||||||
|
pyotp==2.2.6
|
||||||
PyNaCl==1.2.1
|
PyNaCl==1.2.1
|
||||||
python-dateutil==2.6.1
|
python-dateutil==2.6.1
|
||||||
python-gssapi==0.6.4
|
python-gssapi==0.6.4
|
||||||
|
|
Loading…
Reference in New Issue