Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
ee35ca36
Commit
ee35ca36
authored
Jun 05, 2018
by
BaiJiangJie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Feature] 增加功能,安全设置(全局MFA设置,密码强度校验)
parent
526943a0
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
1793 additions
and
179 deletions
+1793
-179
forms.py
apps/common/forms.py
+45
-0
basic_setting.html
apps/common/templates/common/basic_setting.html
+3
-0
email_setting.html
apps/common/templates/common/email_setting.html
+3
-0
ldap_setting.html
apps/common/templates/common/ldap_setting.html
+3
-0
security_setting.html
apps/common/templates/common/security_setting.html
+115
-0
terminal_setting.html
apps/common/templates/common/terminal_setting.html
+5
-0
view_urls.py
apps/common/urls/view_urls.py
+1
-0
views.py
apps/common/views.py
+25
-1
django.mo
apps/i18n/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/i18n/zh/LC_MESSAGES/django.po
+260
-159
settings.py
apps/jumpserver/settings.py
+3
-0
jumpserver.js
apps/static/js/jumpserver.js
+88
-0
pwstrength-bootstrap.js
apps/static/js/pwstrength-bootstrap.js
+977
-0
_base_create_update.html
apps/templates/_base_create_update.html
+1
-0
forms.py
apps/users/forms.py
+1
-1
_user.html
apps/users/templates/users/_user.html
+1
-0
reset_password.html
apps/users/templates/users/reset_password.html
+43
-3
user_password_update.html
apps/users/templates/users/user_password_update.html
+41
-0
user_profile.html
apps/users/templates/users/user_profile.html
+4
-1
user_update.html
apps/users/templates/users/user_update.html
+45
-0
utils.py
apps/users/utils.py
+61
-0
login.py
apps/users/views/login.py
+31
-12
user.py
apps/users/views/user.py
+37
-2
No files found.
apps/common/forms.py
View file @
ee35ca36
...
...
@@ -168,3 +168,48 @@ class TerminalSettingForm(BaseForm):
)
)
class
SecuritySettingForm
(
BaseForm
):
# MFA全局设置
SECURITY_MFA_AUTH
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
label
=
_
(
"MFA Secondary certification"
),
help_text
=
_
(
'After opening, the user login must use MFA secondary '
'authentication (valid for all users, including administrators)'
)
)
# 最小长度
SECURITY_PASSWORD_MIN_LENGTH
=
forms
.
IntegerField
(
initial
=
6
,
label
=
_
(
"Password minimum length"
),
)
# 大写字母
SECURITY_PASSWORD_UPPER_CASE
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
label
=
_
(
"Must contain capital letters"
),
help_text
=
_
(
'After opening, the user password changes '
'and resets must contain uppercase letters'
)
)
# 小写字母
SECURITY_PASSWORD_LOWER_CASE
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
label
=
_
(
"Must contain lowercase letters"
),
help_text
=
_
(
'After opening, the user password changes '
'and resets must contain lowercase letters'
)
)
# 数字
SECURITY_PASSWORD_NUMBER
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
label
=
_
(
"Must contain numeric characters"
),
help_text
=
_
(
'After opening, the user password changes '
'and resets must contain numeric characters'
)
)
# 特殊字符
SECURITY_PASSWORD_SPECIAL_CHAR
=
forms
.
BooleanField
(
initial
=
False
,
required
=
False
,
label
=
_
(
"Must contain special characters"
),
help_text
=
_
(
'After opening, the user password changes '
'and resets must contain special characters'
)
)
apps/common/templates/common/basic_setting.html
View file @
ee35ca36
...
...
@@ -23,6 +23,9 @@
<li>
<a
href=
"{% url 'settings:terminal-setting' %}"
class=
"text-center"
><i
class=
"fa fa-hdd-o"
></i>
{% trans 'Terminal setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:security-setting' %}"
class=
"text-center"
><i
class=
"fa fa-lock"
></i>
{% trans 'Security setting' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
...
...
apps/common/templates/common/email_setting.html
View file @
ee35ca36
...
...
@@ -23,6 +23,9 @@
<li>
<a
href=
"{% url 'settings:terminal-setting' %}"
class=
"text-center"
><i
class=
"fa fa-hdd-o"
></i>
{% trans 'Terminal setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:security-setting' %}"
class=
"text-center"
><i
class=
"fa fa-lock"
></i>
{% trans 'Security setting' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
...
...
apps/common/templates/common/ldap_setting.html
View file @
ee35ca36
...
...
@@ -23,6 +23,9 @@
<li>
<a
href=
"{% url 'settings:terminal-setting' %}"
class=
"text-center"
><i
class=
"fa fa-hdd-o"
></i>
{% trans 'Terminal setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:security-setting' %}"
class=
"text-center"
><i
class=
"fa fa-lock"
></i>
{% trans 'Security setting' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
...
...
apps/common/templates/common/security_setting.html
0 → 100644
View file @
ee35ca36
{% extends 'base.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% block content %}
<div
class=
"wrapper wrapper-content animated fadeInRight"
>
<div
class=
"row"
>
<div
class=
"col-sm-12"
>
<div
class=
"ibox float-e-margins"
>
<div
class=
"panel-options"
>
<ul
class=
"nav nav-tabs"
>
<li>
<a
href=
""
class=
"text-center"
><i
class=
"fa fa-cubes"
></i>
{% trans 'Basic setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:email-setting' %}"
class=
"text-center"
><i
class=
"fa fa-envelope"
></i>
{% trans 'Email setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:ldap-setting' %}"
class=
"text-center"
><i
class=
"fa fa-archive"
></i>
{% trans 'LDAP setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:terminal-setting' %}"
class=
"text-center"
><i
class=
"fa fa-hdd-o"
></i>
{% trans 'Terminal setting' %}
</a>
</li>
<li
class=
"active"
>
<a
href=
"{% url 'settings:security-setting' %}"
class=
"text-center"
><i
class=
"fa fa-lock"
></i>
{% trans 'Security setting' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
<div
class=
"col-sm-12"
style=
"padding-left:0"
>
<div
class=
"ibox-content"
style=
"border-width: 0;padding-top: 40px;"
>
<form
action=
""
method=
"post"
class=
"form-horizontal"
>
{% if form.non_field_errors %}
<div
class=
"alert alert-danger"
>
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<h3>
{% trans "MFA setting" %}
</h3>
{% for field in form %}
{% if forloop.counter == 2 %}
<div
class=
"hr-line-dashed"
></div>
<h3>
{% trans "Password check rule" %}
</h3>
{% endif %}
{% if not field.field|is_bool_field %}
{% bootstrap_field field layout="horizontal" %}
{% else %}
<div
class=
"form-group"
>
<label
for=
"{{ field.id_for_label }}"
class=
"col-sm-2 control-label"
>
{{ field.label }}
</label>
<div
class=
"col-sm-8"
>
<div
class=
"col-sm-1"
>
{{ field }}
</div>
<div
class=
"col-sm-9"
>
<span
class=
"help-block"
>
{{ field.help_text }}
</span>
</div>
</div>
</div>
{% endif %}
{% endfor %}
<div
class=
"hr-line-dashed"
></div>
<div
class=
"form-group"
>
<div
class=
"col-sm-4 col-sm-offset-2"
>
<button
class=
"btn btn-default"
type=
"reset"
>
{% trans 'Reset' %}
</button>
<button
id=
"submit_button"
class=
"btn btn-primary"
type=
"submit"
>
{% trans 'Submit' %}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$
(
document
).
ready
(
function
()
{
})
.
on
(
"click"
,
".btn-test"
,
function
()
{
var
data
=
{};
var
form
=
$
(
"form"
).
serializeArray
();
$
.
each
(
form
,
function
(
i
,
field
)
{
data
[
field
.
name
]
=
field
.
value
;
});
var
the_url
=
"{% url 'api-common:mail-testing' %}"
;
function
error
(
message
)
{
toastr
.
error
(
message
)
}
function
success
(
message
)
{
toastr
.
success
(
message
.
msg
)
}
APIUpdateAttr
({
url
:
the_url
,
body
:
JSON
.
stringify
(
data
),
method
:
"POST"
,
flash_message
:
false
,
success
:
success
,
error
:
error
});
})
</script>
{% endblock %}
apps/common/templates/common/terminal_setting.html
View file @
ee35ca36
...
...
@@ -27,6 +27,9 @@
<a
href=
"{% url 'settings:terminal-setting' %}"
class=
"text-center"
><i
class=
"fa fa-hdd-o"
></i>
{% trans 'Terminal setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:security-setting' %}"
class=
"text-center"
><i
class=
"fa fa-lock"
></i>
{% trans 'Security setting' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
...
...
@@ -39,6 +42,7 @@
</div>
{% endif %}
{% csrf_token %}
<h3>
{% trans "Basic setting" %}
</h3>
{% for field in form %}
{% if not field.field|is_bool_field %}
...
...
@@ -60,6 +64,7 @@
{% endfor %}
<div
class=
"hr-line-dashed"
></div>
<h3>
{% trans "Command storage" %}
</h3>
<table
class=
"table table-hover "
id=
"task-history-list-table"
>
<thead>
...
...
apps/common/urls/view_urls.py
View file @
ee35ca36
...
...
@@ -11,4 +11,5 @@ urlpatterns = [
url
(
r'^email/$'
,
views
.
EmailSettingView
.
as_view
(),
name
=
'email-setting'
),
url
(
r'^ldap/$'
,
views
.
LDAPSettingView
.
as_view
(),
name
=
'ldap-setting'
),
url
(
r'^terminal/$'
,
views
.
TerminalSettingView
.
as_view
(),
name
=
'terminal-setting'
),
url
(
r'^security/$'
,
views
.
SecuritySettingView
.
as_view
(),
name
=
'security-setting'
),
]
apps/common/views.py
View file @
ee35ca36
...
...
@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from
django.conf
import
settings
from
.forms
import
EmailSettingForm
,
LDAPSettingForm
,
BasicSettingForm
,
\
TerminalSettingForm
TerminalSettingForm
,
SecuritySettingForm
from
.mixins
import
AdminUserRequiredMixin
from
.signals
import
ldap_auth_enable
...
...
@@ -122,3 +122,27 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
return
render
(
request
,
self
.
template_name
,
context
)
class
SecuritySettingView
(
AdminUserRequiredMixin
,
TemplateView
):
form_class
=
SecuritySettingForm
template_name
=
"common/security_setting.html"
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'app'
:
_
(
'Settings'
),
'action'
:
_
(
'Security setting'
),
'form'
:
self
.
form_class
(),
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
post
(
self
,
request
):
form
=
self
.
form_class
(
request
.
POST
)
if
form
.
is_valid
():
form
.
save
()
msg
=
_
(
"Update setting successfully, please restart program"
)
messages
.
success
(
request
,
msg
)
return
redirect
(
'settings:security-setting'
)
else
:
context
=
self
.
get_context_data
()
context
.
update
({
"form"
:
form
})
return
render
(
request
,
self
.
template_name
,
context
)
apps/i18n/zh/LC_MESSAGES/django.mo
View file @
ee35ca36
No preview for this file type
apps/i18n/zh/LC_MESSAGES/django.po
View file @
ee35ca36
...
...
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-0
5-25 18:11
+0800\n"
"POT-Creation-Date: 2018-0
6-05 17:03
+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
...
...
@@ -21,15 +21,15 @@ msgstr ""
msgid "New node {}"
msgstr "新节点 {}"
#: assets/api/node.py:24
2
#: assets/api/node.py:24
1
msgid "更新节点资产硬件信息: {}"
msgstr ""
#: assets/api/node.py:25
5
#: assets/api/node.py:25
4
msgid "测试节点下资产是否可连接: {}"
msgstr ""
#: assets/forms/asset.py:24 assets/models/asset.py:
66
assets/models/user.py:103
#: assets/forms/asset.py:24 assets/models/asset.py:
74
assets/models/user.py:103
#: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/system_user_detail.html:175 perms/models.py:33
...
...
@@ -37,7 +37,7 @@ msgid "Nodes"
msgstr "节点管理"
#: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109
#: assets/forms/asset.py:113 assets/models/asset.py:7
0
#: assets/forms/asset.py:113 assets/models/asset.py:7
9
#: assets/models/cluster.py:19 assets/models/user.py:72
#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25
msgid "Admin user"
...
...
@@ -46,14 +46,14 @@ msgstr "管理用户"
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125
#: assets/templates/assets/asset_create.html:35
#: assets/templates/assets/asset_create.html:37
#: assets/templates/assets/asset_list.html:7
4
#: assets/templates/assets/asset_list.html:7
5
#: assets/templates/assets/asset_update.html:40
#: assets/templates/assets/asset_update.html:42
#: assets/templates/assets/user_asset_list.html:34
msgid "Label"
msgstr "标签"
#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:
65
#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:
70
#: assets/models/domain.py:46
msgid "Domain"
msgstr "网域"
...
...
@@ -90,7 +90,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
msgid "Select assets"
msgstr "选择资产"
#: assets/forms/asset.py:105 assets/models/asset.py:6
3
#: assets/forms/asset.py:105 assets/models/asset.py:6
6
#: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58
...
...
@@ -99,7 +99,7 @@ msgid "Port"
msgstr "端口"
#: assets/forms/domain.py:14 assets/forms/label.py:13
#: assets/models/asset.py:
183
assets/templates/assets/admin_user_list.html:25
#: assets/models/asset.py:
229
assets/templates/assets/admin_user_list.html:25
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:15
#: assets/templates/assets/label_list.html:16
...
...
@@ -129,8 +129,8 @@ msgstr "资产"
#: assets/templates/assets/label_list.html:14
#: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:26 common/models.py:26
#: common/templates/common/terminal_setting.html:
67
#: common/templates/common/terminal_setting.html:
85
ops/models/adhoc.py:36
#: common/templates/common/terminal_setting.html:
72
#: common/templates/common/terminal_setting.html:
90
ops/models/adhoc.py:36
#: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35
#: perms/models.py:29 perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:53
...
...
@@ -171,10 +171,10 @@ msgstr "密码或密钥密码"
#: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113
#: users/forms.py:15 users/forms.py:23 users/forms.py:32 users/forms.py:44
#: users/templates/users/login.html:59
#: users/templates/users/reset_password.html:5
2
#: users/templates/users/reset_password.html:5
3
#: users/templates/users/user_create.html:10
#: users/templates/users/user_password_authentication.html:14
#: users/templates/users/user_password_update.html:4
0
#: users/templates/users/user_password_update.html:4
2
#: users/templates/users/user_profile_update.html:40
#: users/templates/users/user_pubkey_update.html:40
msgid "Password"
...
...
@@ -202,11 +202,11 @@ msgid ""
"than 2 system user"
msgstr "高优先级的系统用户将会作为默认登录用户"
#: assets/models/asset.py:6
1
assets/models/domain.py:43
#: assets/models/asset.py:6
2
assets/models/domain.py:43
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61
#: assets/templates/assets/asset_list.html:8
6
#: assets/templates/assets/asset_list.html:8
7
#: assets/templates/assets/domain_gateway_list.html:57
#: assets/templates/assets/system_user_asset.html:50
#: assets/templates/assets/user_asset_list.html:46 common/forms.py:144
...
...
@@ -217,10 +217,10 @@ msgstr "高优先级的系统用户将会作为默认登录用户"
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:6
2
assets/templates/assets/_asset_list_modal.html:45
#: assets/models/asset.py:6
5
assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_list.html:8
5
#: assets/templates/assets/asset_list.html:8
6
#: assets/templates/assets/system_user_asset.html:49
#: assets/templates/assets/user_asset_list.html:45 common/forms.py:143
#: perms/templates/perms/asset_permission_asset.html:54
...
...
@@ -229,82 +229,82 @@ msgstr "IP"
msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:6
4
assets/templates/assets/asset_detail.html:97
#: assets/models/asset.py:6
8
assets/templates/assets/asset_detail.html:97
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:
67
assets/models/domain.py:48
#: assets/models/asset.py:
75
assets/models/domain.py:48
#: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105
msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:
73
assets/templates/assets/asset_detail.html:65
#: assets/models/asset.py:
84
assets/templates/assets/asset_detail.html:65
msgid "Public IP"
msgstr "公网IP"
#: assets/models/asset.py:
74
assets/templates/assets/asset_detail.html:113
#: assets/models/asset.py:
86
assets/templates/assets/asset_detail.html:113
msgid "Asset number"
msgstr "资产编号"
#: assets/models/asset.py:
77
assets/templates/assets/asset_detail.html:77
#: assets/models/asset.py:
90
assets/templates/assets/asset_detail.html:77
msgid "Vendor"
msgstr "制造商"
#: assets/models/asset.py:
78
assets/templates/assets/asset_detail.html:81
#: assets/models/asset.py:
92
assets/templates/assets/asset_detail.html:81
msgid "Model"
msgstr "型号"
#: assets/models/asset.py:
79
assets/templates/assets/asset_detail.html:109
#: assets/models/asset.py:
94
assets/templates/assets/asset_detail.html:109
msgid "Serial number"
msgstr "序列号"
#: assets/models/asset.py:
81
#: assets/models/asset.py:
97
msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:
82
#: assets/models/asset.py:
98
msgid "CPU count"
msgstr "CPU数量"
#: assets/models/asset.py:
83
#: assets/models/asset.py:
99
msgid "CPU cores"
msgstr "CPU核数"
#: assets/models/asset.py:
84
assets/templates/assets/asset_detail.html:89
#: assets/models/asset.py:
101
assets/templates/assets/asset_detail.html:89
msgid "Memory"
msgstr "内存"
#: assets/models/asset.py:
85
#: assets/models/asset.py:
103
msgid "Disk total"
msgstr "硬盘大小"
#: assets/models/asset.py:
86
#: assets/models/asset.py:
105
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:
8
8 assets/templates/assets/asset_detail.html:101
#: assets/models/asset.py:
10
8 assets/templates/assets/asset_detail.html:101
msgid "OS"
msgstr "操作系统"
#: assets/models/asset.py:
89
#: assets/models/asset.py:
110
msgid "OS version"
msgstr "系统版本"
#: assets/models/asset.py:
90
#: assets/models/asset.py:
112
msgid "OS arch"
msgstr "系统架构"
#: assets/models/asset.py:
91
#: assets/models/asset.py:
114
msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:
93
assets/templates/assets/asset_create.html:33
#: assets/models/asset.py:
118
assets/templates/assets/asset_create.html:33
#: assets/templates/assets/asset_detail.html:220
#: assets/templates/assets/asset_update.html:38 templates/_nav.html:27
msgid "Labels"
msgstr "标签管理"
#: assets/models/asset.py:
94
assets/models/base.py:29
#: assets/models/asset.py:
120
assets/models/base.py:29
#: assets/models/cluster.py:28 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:117
...
...
@@ -316,7 +316,7 @@ msgstr "标签管理"
msgid "Created by"
msgstr "创建者"
#: assets/models/asset.py:
95
assets/models/cluster.py:26
#: assets/models/asset.py:
123
assets/models/cluster.py:26
#: assets/models/domain.py:20 assets/models/group.py:22
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/domain_detail.html:68
...
...
@@ -329,7 +329,7 @@ msgstr "创建者"
msgid "Date created"
msgstr "创建日期"
#: assets/models/asset.py:
96
assets/models/base.py:26
#: assets/models/asset.py:
125
assets/models/base.py:26
#: assets/models/cluster.py:29 assets/models/domain.py:18
#: assets/models/domain.py:47 assets/models/group.py:23
#: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72
...
...
@@ -346,7 +346,7 @@ msgstr "创建日期"
#: users/models/user.py:75 users/templates/users/user_detail.html:119
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:12
3
#: users/templates/users/user_profile.html:12
6
msgid "Comment"
msgstr "备注"
...
...
@@ -434,11 +434,11 @@ msgstr "默认资产组"
#: terminal/templates/terminal/session_list.html:71 users/forms.py:281
#: users/models/user.py:30 users/models/user.py:318
#: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:3
39
#: users/templates/users/user_group_list.html:13 users/views/user.py:3
60
msgid "User"
msgstr "用户"
#: assets/models/label.py:18 assets/models/node.py:1
8
#: assets/models/label.py:18 assets/models/node.py:1
6
#: assets/templates/assets/label_list.html:15 common/models.py:27
msgid "Value"
msgstr "值"
...
...
@@ -447,7 +447,7 @@ msgstr "值"
msgid "Category"
msgstr "分类"
#: assets/models/node.py:1
4
#: assets/models/node.py:1
5
msgid "Key"
msgstr ""
...
...
@@ -630,16 +630,17 @@ msgstr "其它"
#: assets/templates/assets/domain_create_update.html:16
#: assets/templates/assets/gateway_create_update.html:58
#: assets/templates/assets/label_create_update.html:18
#: common/templates/common/basic_setting.html:58
#: common/templates/common/email_setting.html:59
#: common/templates/common/ldap_setting.html:59
#: common/templates/common/terminal_setting.html:101
#: common/templates/common/basic_setting.html:61
#: common/templates/common/email_setting.html:62
#: common/templates/common/ldap_setting.html:62
#: common/templates/common/security_setting.html:70
#: common/templates/common/terminal_setting.html:106
#: perms/templates/perms/asset_permission_create_update.html:69
#: terminal/templates/terminal/terminal_update.html:47
#: users/templates/users/_user.html:46
#: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_password_update.html:
58
#: users/templates/users/user_profile.html:18
1
#: users/templates/users/user_password_update.html:
70
#: users/templates/users/user_profile.html:18
4
#: users/templates/users/user_profile_update.html:63
#: users/templates/users/user_pubkey_update.html:70
#: users/templates/users/user_pubkey_update.html:76
...
...
@@ -650,15 +651,16 @@ msgstr "重置"
#: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_list.html:10
7
#: assets/templates/assets/asset_list.html:10
8
#: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:59
#: assets/templates/assets/label_create_update.html:19
#: common/templates/common/basic_setting.html:59
#: common/templates/common/email_setting.html:60
#: common/templates/common/ldap_setting.html:60
#: common/templates/common/terminal_setting.html:103
#: common/templates/common/basic_setting.html:62
#: common/templates/common/email_setting.html:63
#: common/templates/common/ldap_setting.html:63
#: common/templates/common/security_setting.html:71
#: common/templates/common/terminal_setting.html:108
#: perms/templates/perms/asset_permission_create_update.html:70
#: terminal/templates/terminal/session_list.html:124
#: terminal/templates/terminal/terminal_update.html:48
...
...
@@ -666,7 +668,7 @@ msgstr "重置"
#: users/templates/users/forgot_password.html:44
#: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:44
#: users/templates/users/user_password_update.html:
59
#: users/templates/users/user_password_update.html:
71
#: users/templates/users/user_profile_update.html:64
#: users/templates/users/user_pubkey_update.html:77
msgid "Submit"
...
...
@@ -727,7 +729,7 @@ msgstr "测试"
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:85
#: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_list.html:17
4
#: assets/templates/assets/asset_list.html:17
5
#: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:85
...
...
@@ -743,15 +745,15 @@ msgstr "测试"
#: users/templates/users/user_group_detail.html:28
#: users/templates/users/user_group_list.html:43
#: users/templates/users/user_list.html:76
#: users/templates/users/user_profile.html:14
4
#: users/templates/users/user_profile.html:17
3
#: users/templates/users/user_profile.html:14
7
#: users/templates/users/user_profile.html:17
6
msgid "Update"
msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:86
#: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_list.html:17
5
#: assets/templates/assets/asset_list.html:17
6
#: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:86
...
...
@@ -782,7 +784,7 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200
#: assets/templates/assets/asset_list.html:63
6
#: assets/templates/assets/asset_list.html:63
9
#: assets/templates/assets/system_user_detail.html:192
#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108
...
...
@@ -792,7 +794,7 @@ msgstr "选择节点"
#: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:199
#: users/templates/users/user_profile.html:21
5
#: users/templates/users/user_profile.html:21
8
msgid "Confirm"
msgstr "确认"
...
...
@@ -814,7 +816,7 @@ msgid "Ratio"
msgstr "比例"
#: assets/templates/assets/admin_user_list.html:30
#: assets/templates/assets/asset_list.html:9
0
#: assets/templates/assets/asset_list.html:9
1
#: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:18
#: assets/templates/assets/label_list.html:17
...
...
@@ -843,19 +845,19 @@ msgstr "硬盘"
#: assets/templates/assets/asset_detail.html:121
#: users/templates/users/user_detail.html:111
#: users/templates/users/user_profile.html:
97
#: users/templates/users/user_profile.html:
100
msgid "Date joined"
msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:137
#: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:130
#: users/templates/users/user_profile.html:13
5
#: users/templates/users/user_profile.html:13
8
msgid "Quick modify"
msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:143
#: assets/templates/assets/asset_list.html:8
8
#: assets/templates/assets/asset_list.html:8
9
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:35
#: perms/models.py:79
#: perms/templates/perms/asset_permission_create_update.html:47
...
...
@@ -885,97 +887,97 @@ msgstr "刷新"
msgid "Update successfully!"
msgstr "更新成功"
#: assets/templates/assets/asset_list.html:6
2
assets/views/asset.py:97
#: assets/templates/assets/asset_list.html:6
3
assets/views/asset.py:97
msgid "Create asset"
msgstr "创建资产"
#: assets/templates/assets/asset_list.html:6
6
#: assets/templates/assets/asset_list.html:6
7
#: users/templates/users/user_list.html:7
msgid "Import"
msgstr "导入"
#: assets/templates/assets/asset_list.html:
69
#: assets/templates/assets/asset_list.html:
70
#: users/templates/users/user_list.html:10
msgid "Export"
msgstr "导出"
#: assets/templates/assets/asset_list.html:8
7
#: assets/templates/assets/asset_list.html:8
8
msgid "Hardware"
msgstr "硬件"
#: assets/templates/assets/asset_list.html:
99
#: assets/templates/assets/asset_list.html:
100
#: users/templates/users/user_list.html:37
msgid "Delete selected"
msgstr "批量删除"
#: assets/templates/assets/asset_list.html:10
0
#: assets/templates/assets/asset_list.html:10
1
#: users/templates/users/user_list.html:38
msgid "Update selected"
msgstr "批量更新"
#: assets/templates/assets/asset_list.html:10
1
#: assets/templates/assets/asset_list.html:10
2
msgid "Remove from this node"
msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:10
2
#: assets/templates/assets/asset_list.html:10
3
#: users/templates/users/user_list.html:39
msgid "Deactive selected"
msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:10
3
#: assets/templates/assets/asset_list.html:10
4
#: users/templates/users/user_list.html:40
msgid "Active selected"
msgstr "激活所选"
#: assets/templates/assets/asset_list.html:12
0
#: assets/templates/assets/asset_list.html:12
1
msgid "Add node"
msgstr "新建节点"
#: assets/templates/assets/asset_list.html:12
1
#: assets/templates/assets/asset_list.html:12
2
msgid "Rename node"
msgstr "重命名节点"
#: assets/templates/assets/asset_list.html:12
2
#: assets/templates/assets/asset_list.html:12
3
msgid "Delete node"
msgstr "删除节点"
#: assets/templates/assets/asset_list.html:12
4
#: assets/templates/assets/asset_list.html:12
5
msgid "Add assets to node"
msgstr "添加资产到节点"
#: assets/templates/assets/asset_list.html:12
5
#: assets/templates/assets/asset_list.html:12
6
msgid "Move assets to node"
msgstr "移动资产到节点"
#: assets/templates/assets/asset_list.html:12
7
#: assets/templates/assets/asset_list.html:12
8
msgid "Refresh node hardware info"
msgstr "更新节点资产硬件信息"
#: assets/templates/assets/asset_list.html:12
8
#: assets/templates/assets/asset_list.html:12
9
msgid "Test node connective"
msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:13
0
#: assets/templates/assets/asset_list.html:13
1
msgid "Display only current node assets"
msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:13
1
#: assets/templates/assets/asset_list.html:13
2
msgid "Displays all child node assets"
msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:21
7
#: assets/templates/assets/asset_list.html:21
8
msgid "Create node failed"
msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:2
29
#: assets/templates/assets/asset_list.html:2
30
msgid "Have child node, cancel"
msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:23
1
#: assets/templates/assets/asset_list.html:23
2
msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:63
1
#: assets/templates/assets/asset_list.html:63
4
#: assets/templates/assets/system_user_list.html:133
#: users/templates/users/user_detail.html:357
#: users/templates/users/user_detail.html:382
...
...
@@ -984,20 +986,20 @@ msgstr "存在资产,不能删除"
msgid "Are you sure?"
msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:63
2
#: assets/templates/assets/asset_list.html:63
5
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:64
0
#: assets/templates/assets/asset_list.html:64
3
msgid "Asset Deleted."
msgstr "已被删除"
#: assets/templates/assets/asset_list.html:64
1
#: assets/templates/assets/asset_list.html:64
6
#: assets/templates/assets/asset_list.html:64
4
#: assets/templates/assets/asset_list.html:64
9
msgid "Asset Delete"
msgstr "删除"
#: assets/templates/assets/asset_list.html:64
5
#: assets/templates/assets/asset_list.html:64
8
msgid "Asset Deleting failed."
msgstr "删除失败"
...
...
@@ -1033,8 +1035,8 @@ msgstr "创建网关"
#: assets/templates/assets/domain_gateway_list.html:87
#: assets/templates/assets/domain_gateway_list.html:89
#: common/templates/common/email_setting.html:
58
#: common/templates/common/ldap_setting.html:
58
#: common/templates/common/email_setting.html:
61
#: common/templates/common/ldap_setting.html:
61
msgid "Test connection"
msgstr "测试连接"
...
...
@@ -1376,7 +1378,7 @@ msgstr "密码认证"
msgid "Public key auth"
msgstr "密钥认证"
#: common/forms.py:159 common/templates/common/terminal_setting.html:6
3
#: common/forms.py:159 common/templates/common/terminal_setting.html:6
8
#: terminal/forms.py:30 terminal/models.py:20
msgid "Command storage"
msgstr "命令存储"
...
...
@@ -1387,7 +1389,7 @@ msgid ""
"other storage and some terminal using"
msgstr "设置终端命令存储,default是默认用的存储方式"
#: common/forms.py:165 common/templates/common/terminal_setting.html:8
1
#: common/forms.py:165 common/templates/common/terminal_setting.html:8
6
#: terminal/forms.py:35 terminal/models.py:21
msgid "Replay storage"
msgstr "录像存储"
...
...
@@ -1398,6 +1400,60 @@ msgid ""
"other storage and some terminal using"
msgstr "设置终端录像存储,default是默认用的存储方式"
#: common/forms.py:176
msgid "MFA Secondary certification"
msgstr "MFA 二次认证"
#: common/forms.py:178
msgid ""
"After opening, the user login must use MFA secondary authentication (valid "
"for all users, including administrators)"
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
#: common/forms.py:184
msgid "Password minimum length"
msgstr "密码最小长度 "
#: common/forms.py:190
msgid "Must contain capital letters"
msgstr "必须包含大写字母"
#: common/forms.py:192
msgid ""
"After opening, the user password changes and resets must contain uppercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: common/forms.py:198
msgid "Must contain lowercase letters"
msgstr "必须包含小写字母"
#: common/forms.py:199
msgid ""
"After opening, the user password changes and resets must contain lowercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: common/forms.py:205
msgid "Must contain numeric characters"
msgstr "必须包含数字字符"
#: common/forms.py:206
msgid ""
"After opening, the user password changes and resets must contain numeric "
"characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: common/forms.py:212
msgid "Must contain special characters"
msgstr "必须包含特殊字符"
#: common/forms.py:213
msgid ""
"After opening, the user password changes and resets must contain special "
"characters"
msgstr "开启后,用户密码修改、重置必须包含特殊字符"
#: common/mixins.py:29
msgid "is discard"
msgstr ""
...
...
@@ -1413,14 +1469,16 @@ msgstr "启用"
#: common/templates/common/basic_setting.html:15
#: common/templates/common/email_setting.html:15
#: common/templates/common/ldap_setting.html:15
#: common/templates/common/security_setting.html:15
#: common/templates/common/terminal_setting.html:16
#: common/templates/common/terminal_setting.html:4
2
common/views.py:22
#: common/templates/common/terminal_setting.html:4
6
common/views.py:22
msgid "Basic setting"
msgstr "基本设置"
#: common/templates/common/basic_setting.html:18
#: common/templates/common/email_setting.html:18
#: common/templates/common/ldap_setting.html:18
#: common/templates/common/security_setting.html:18
#: common/templates/common/terminal_setting.html:20 common/views.py:48
msgid "Email setting"
msgstr "邮件设置"
...
...
@@ -1428,6 +1486,7 @@ msgstr "邮件设置"
#: common/templates/common/basic_setting.html:21
#: common/templates/common/email_setting.html:21
#: common/templates/common/ldap_setting.html:21
#: common/templates/common/security_setting.html:21
#: common/templates/common/terminal_setting.html:24 common/views.py:74
msgid "LDAP setting"
msgstr "LDAP设置"
...
...
@@ -1435,12 +1494,29 @@ msgstr "LDAP设置"
#: common/templates/common/basic_setting.html:24
#: common/templates/common/email_setting.html:24
#: common/templates/common/ldap_setting.html:24
#: common/templates/common/security_setting.html:24
#: common/templates/common/terminal_setting.html:28 common/views.py:104
msgid "Terminal setting"
msgstr "终端设置"
#: common/templates/common/terminal_setting.html:68
#: common/templates/common/terminal_setting.html:86
#: common/templates/common/basic_setting.html:27
#: common/templates/common/email_setting.html:27
#: common/templates/common/ldap_setting.html:27
#: common/templates/common/security_setting.html:27
#: common/templates/common/terminal_setting.html:31 common/views.py:132
msgid "Security setting"
msgstr "安全设置"
#: common/templates/common/security_setting.html:42
msgid "MFA setting"
msgstr "MFA 设置"
#: common/templates/common/security_setting.html:46
msgid "Password check rule"
msgstr "密码校验规则"
#: common/templates/common/terminal_setting.html:73
#: common/templates/common/terminal_setting.html:91
#: users/templates/users/login_log_list.html:50
msgid "Type"
msgstr "类型"
...
...
@@ -1450,11 +1526,12 @@ msgid "Special char not allowed"
msgstr "不能包含特殊字符"
#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:103
#: templates/_nav.html:81
#:
common/views.py:131
templates/_nav.html:81
msgid "Settings"
msgstr "系统设置"
#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:116
#: common/views.py:142
msgid "Update setting successfully, please restart program"
msgstr "更新设置成功, 请手动重启程序"
...
...
@@ -1754,7 +1831,7 @@ msgstr ""
#: perms/models.py:37 perms/models.py:80
#: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:80 users/templates/users/user_detail.html:103
#: users/templates/users/user_profile.html:10
5
#: users/templates/users/user_profile.html:10
8
msgid "Date expired"
msgstr "失效日期"
...
...
@@ -1884,11 +1961,11 @@ msgstr "文档"
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:121
#: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:3
7
#: users/templates/users/user_password_update.html:3
9
#: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:3
2
2
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:3
4
2
msgid "Profile"
msgstr "个人信息"
...
...
@@ -1945,13 +2022,13 @@ msgstr "关闭"
#: 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/login.py:2
63 users/views/login.py:321 users/views/user.py:64
#: users/views/user.py:
79 users/views/user.py:99 users/views/user.py:155
#: users/views/user.py:3
10 users/views/user.py:357 users/views/user.py:379
#: users/views/login.py:2
84 users/views/login.py:342 users/views/user.py:65
#: users/views/user.py:
80 users/views/user.py:102 users/views/user.py:173
#: users/views/user.py:3
28 users/views/user.py:379 users/views/user.py:414
msgid "Users"
msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:6
5
#: templates/_nav.html:13 users/views/user.py:6
6
msgid "User list"
msgstr "用户列表"
...
...
@@ -2356,7 +2433,7 @@ msgstr "复制你的公钥到这里"
#: users/forms.py:231 users/models/user.py:72
#: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:4
3
#: users/templates/users/user_password_update.html:4
5
#: users/templates/users/user_profile.html:68
#: users/templates/users/user_profile_update.html:43
#: users/templates/users/user_pubkey_update.html:43
...
...
@@ -2396,13 +2473,13 @@ msgid "Application"
msgstr "应用程序"
#: users/models/user.py:34 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:156
#: users/templates/users/user_profile.html:159
#: users/templates/users/user_profile.html:162
msgid "Disable"
msgstr "禁用"
#: users/models/user.py:35 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:16
3
#: users/templates/users/user_profile.html:16
6
msgid "Enable"
msgstr "启用"
...
...
@@ -2539,22 +2616,34 @@ msgstr "6位数字"
msgid "Can't provide security? Please contact the administrator!"
msgstr "如果不能提供MFA验证码,请联系管理员!"
#: users/templates/users/reset_password.html:4
5
#: users/templates/users/user_detail.html:348 users/utils.py:
76
#: users/templates/users/reset_password.html:4
6
#: users/templates/users/user_detail.html:348 users/utils.py:
80
msgid "Reset password"
msgstr "重置密码"
#: users/templates/users/reset_password.html:55
#: users/templates/users/reset_password.html:59
#: users/templates/users/user_password_update.html:60
#: users/templates/users/user_update.html:12
msgid "Your password must satisfy"
msgstr "您的密码必须满足:"
#: users/templates/users/reset_password.html:60
#: users/templates/users/user_password_update.html:61
#: users/templates/users/user_update.html:13
msgid "Password strength"
msgstr "密码强度:"
#: users/templates/users/reset_password.html:66
msgid "Password again"
msgstr "再次输入密码"
#: users/templates/users/reset_password.html:
57
#: users/templates/users/reset_password.html:
68
#: users/templates/users/user_profile.html:20
msgid "Setting"
msgstr "设置"
#: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:16 users/views/user.py:
79
#: users/templates/users/user_list.html:16 users/views/user.py:
80
msgid "Create user"
msgstr "创建用户"
...
...
@@ -2563,7 +2652,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:1
56
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:1
74
msgid "User detail"
msgstr "用户详情"
...
...
@@ -2583,7 +2672,7 @@ msgid "Disabled"
msgstr "禁用"
#: users/templates/users/user_detail.html:115
#: users/templates/users/user_profile.html:10
1
#: users/templates/users/user_profile.html:10
4
msgid "Last login"
msgstr "最后登录"
...
...
@@ -2631,14 +2720,14 @@ msgid "This will reset the user public key and send a reset mail"
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
#: users/templates/users/user_detail.html:400
#: users/templates/users/user_profile.html:20
4
#: users/templates/users/user_profile.html:20
7
msgid "Successfully updated the SSH public key."
msgstr "更新ssh密钥成功"
#: users/templates/users/user_detail.html:401
#: users/templates/users/user_detail.html:405
#: users/templates/users/user_profile.html:20
5
#: users/templates/users/user_profile.html:21
0
#: users/templates/users/user_profile.html:20
8
#: users/templates/users/user_profile.html:21
3
msgid "User SSH public key update"
msgstr "ssh密钥"
...
...
@@ -2694,28 +2783,32 @@ msgstr "删除"
msgid "User Deleting failed."
msgstr "用户删除失败"
#: users/templates/users/user_profile.html:109 users/views/user.py:185
#: users/views/user.py:239
#: users/templates/users/user_profile.html:95
msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录"
#: users/templates/users/user_profile.html:112 users/views/user.py:203
#: users/views/user.py:257
msgid "User groups"
msgstr "用户组"
#: users/templates/users/user_profile.html:14
1
#: users/templates/users/user_profile.html:14
4
msgid "Update password"
msgstr "更改密码"
#: users/templates/users/user_profile.html:1
49
#: users/templates/users/user_profile.html:1
52
msgid "Update MFA settings"
msgstr "更改MFA设置"
#: users/templates/users/user_profile.html:17
0
#: users/templates/users/user_profile.html:17
3
msgid "Update SSH public key"
msgstr "更改SSH密钥"
#: users/templates/users/user_profile.html:1
78
#: users/templates/users/user_profile.html:1
81
msgid "Reset public key and download"
msgstr "重置并下载SSH密钥"
#: users/templates/users/user_profile.html:2
08
#: users/templates/users/user_profile.html:2
11
msgid "Failed to update SSH public key."
msgstr "更新密钥失败"
...
...
@@ -2735,15 +2828,15 @@ msgstr "更新密钥"
msgid "Or reset by server"
msgstr "或者重置并下载密钥"
#: users/templates/users/user_update.html:4 users/views/user.py:
99
#: users/templates/users/user_update.html:4 users/views/user.py:
103
msgid "Update user"
msgstr "更新用户"
#: users/utils.py:
37
#: users/utils.py:
41
msgid "Create account successfully"
msgstr "创建账户成功"
#: users/utils.py:
39
#: users/utils.py:
43
#, fuzzy, python-format
msgid ""
"\n"
...
...
@@ -2788,7 +2881,7 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:
78
#: users/utils.py:
82
#, python-format
msgid ""
"\n"
...
...
@@ -2832,11 +2925,11 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:1
09
#: users/utils.py:1
13
msgid "SSH Key Reset"
msgstr "重置ssh密钥"
#: users/utils.py:11
1
#: users/utils.py:11
5
#, python-format
msgid ""
"\n"
...
...
@@ -2861,18 +2954,22 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:14
4
#: users/utils.py:14
8
msgid "User not exist"
msgstr "用户不存在"
#: users/utils.py:1
46
#: users/utils.py:1
50
msgid "Disabled or expired"
msgstr "禁用或失效"
#: users/utils.py:1
59
#: users/utils.py:1
63
msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法"
#: users/utils.py:290 users/utils.py:300
msgid "Bit"
msgstr " 位"
#: users/views/group.py:29
msgid "User group list"
msgstr "用户组列表"
...
...
@@ -2885,99 +2982,103 @@ msgstr "更新用户组"
msgid "User group granted asset"
msgstr "用户组授权资产"
#: users/views/login.py:
59
#: users/views/login.py:
62
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:1
25 users/views/user.py:464 users/views/user.py:489
#: users/views/login.py:1
35 users/views/user.py:499 users/views/user.py:524
msgid "MFA code invalid"
msgstr "MFA码认证失败"
#: users/views/login.py:1
5
1
#: users/views/login.py:1
6
1
msgid "Logout success"
msgstr "退出登录成功"
#: users/views/login.py:1
5
2
#: users/views/login.py:1
6
2
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:1
6
8
#: users/views/login.py:1
7
8
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:1
8
1
#: users/views/login.py:1
9
1
msgid "Send reset password message"
msgstr "发送重置密码邮件"
#: users/views/login.py:1
8
2
#: users/views/login.py:1
9
2
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:
19
5
#: users/views/login.py:
20
5
msgid "Reset password success"
msgstr "重置密码成功"
#: users/views/login.py:
19
6
#: users/views/login.py:
20
6
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:2
13 users/views/login.py:226
#: users/views/login.py:2
27 users/views/login.py:240
msgid "Token invalid or expired"
msgstr "Token错误或失效"
#: users/views/login.py:2
22
#: users/views/login.py:2
36
msgid "Password not same"
msgstr "密码不一致"
#: users/views/login.py:263
#: users/views/login.py:246 users/views/user.py:115 users/views/user.py:397
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
#: users/views/login.py:284
msgid "First login"
msgstr "首次登陆"
#: users/views/login.py:3
22
#: users/views/login.py:3
43
msgid "Login log list"
msgstr "登录日志"
#: users/views/user.py:1
09
#: users/views/user.py:1
27
msgid "Bulk update user success"
msgstr "批量更新用户成功"
#: users/views/user.py:2
14
#: users/views/user.py:2
32
msgid "Invalid file."
msgstr "文件不合法"
#: users/views/user.py:3
11
#: users/views/user.py:3
29
msgid "User granted assets"
msgstr "用户授权资产"
#: users/views/user.py:3
40
#: users/views/user.py:3
61
msgid "Profile setting"
msgstr "个人信息设置"
#: users/views/user.py:3
58
#: users/views/user.py:3
80
msgid "Password update"
msgstr "密码更新"
#: users/views/user.py:
380
#: users/views/user.py:
415
msgid "Public key update"
msgstr "密钥更新"
#: users/views/user.py:4
21
#: users/views/user.py:4
56
msgid "Password invalid"
msgstr "用户名或密码无效"
#: users/views/user.py:5
15
#: users/views/user.py:5
50
msgid "MFA enable success"
msgstr "MFA 绑定成功"
#: users/views/user.py:5
16
#: users/views/user.py:5
51
msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:5
18
#: users/views/user.py:5
53
msgid "MFA disable success"
msgstr "MFA 解绑成功"
#: users/views/user.py:5
19
#: users/views/user.py:5
54
msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面"
apps/jumpserver/settings.py
View file @
ee35ca36
...
...
@@ -401,6 +401,9 @@ TERMINAL_REPLAY_STORAGE = {
},
}
DEFAULT_PASSWORD_MIN_LENGTH
=
6
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3
=
{
'horizontal_label_class'
:
'col-md-2'
,
...
...
apps/static/js/jumpserver.js
View file @
ee35ca36
...
...
@@ -609,3 +609,91 @@ function setUrlParam(url, name, value) {
}
return
url
}
// 校验密码-改变规则颜色
function
checkPasswordRules
(
password
,
minLength
)
{
if
(
wordMinLength
(
password
,
minLength
))
{
$
(
'#rule_SECURITY_PASSWORD_MIN_LENGTH'
).
css
(
'color'
,
'green'
)
}
else
{
$
(
'#rule_SECURITY_PASSWORD_MIN_LENGTH'
).
css
(
'color'
,
'#908a8a'
)
}
if
(
wordUpperCase
(
password
))
{
$
(
'#rule_SECURITY_PASSWORD_UPPER_CASE'
).
css
(
'color'
,
'green'
);
}
else
{
$
(
'#rule_SECURITY_PASSWORD_UPPER_CASE'
).
css
(
'color'
,
'#908a8a'
)
}
if
(
wordLowerCase
(
password
))
{
$
(
'#rule_SECURITY_PASSWORD_LOWER_CASE'
).
css
(
'color'
,
'green'
)
}
else
{
$
(
'#rule_SECURITY_PASSWORD_LOWER_CASE'
).
css
(
'color'
,
'#908a8a'
)
}
if
(
wordNumber
(
password
))
{
$
(
'#rule_SECURITY_PASSWORD_NUMBER'
).
css
(
'color'
,
'green'
)
}
else
{
$
(
'#rule_SECURITY_PASSWORD_NUMBER'
).
css
(
'color'
,
'#908a8a'
)
}
if
(
wordSpecialChar
(
password
))
{
$
(
'#rule_SECURITY_PASSWORD_SPECIAL_CHAR'
).
css
(
'color'
,
'green'
)
}
else
{
$
(
'#rule_SECURITY_PASSWORD_SPECIAL_CHAR'
).
css
(
'color'
,
'#908a8a'
)
}
}
// 最小长度
function
wordMinLength
(
word
,
minLength
)
{
//var minLength = {{ min_length }};
var
re
=
new
RegExp
(
"^(.{"
+
minLength
+
",})$"
);
return
word
.
match
(
re
)
}
// 大写字母
function
wordUpperCase
(
word
)
{
return
word
.
match
(
/
([
A-Z
]
+
)
/
)
}
// 小写字母
function
wordLowerCase
(
word
)
{
return
word
.
match
(
/
([
a-z
]
+
)
/
)
}
// 数字字符
function
wordNumber
(
word
)
{
return
word
.
match
(
/
([\d]
+
)
/
)
}
// 特殊字符
function
wordSpecialChar
(
word
)
{
return
word
.
match
(
/
[
`,~,!,@,#,
\$
,%,
\^
,&,
\*
,
\(
,
\)
,
\-
,_,=,
\+
,
\{
,
\}
,
\[
,
\]
,
\|
,
\\
,;,',:,",
\,
,
\.
,<,>,
\/
,
\?]
+/
)
}
// 显示弹窗密码规则
function
popoverPasswordRules
(
password_check_rules
,
$el
)
{
var
message
=
""
;
jQuery
.
each
(
password_check_rules
,
function
(
idx
,
rules
)
{
message
+=
"<li id="
+
rules
.
id
+
" style='list-style-type:none;'> <i class='fa fa-check-circle-o' style='margin-right:10px;' ></i>"
+
rules
.
label
+
"</li>"
;
});
//$('#id_password_rules').html(message);
$el
.
html
(
message
)
}
// 初始化弹窗popover
function
initPopover
(
$container
,
$progress
,
$idPassword
,
$el
,
password_check_rules
){
options
=
{};
// User Interface
options
.
ui
=
{
container
:
$container
,
viewports
:
{
progress
:
$progress
//errors: $('.popover-content')
},
showProgressbar
:
true
,
showVerdictsInsideProgressBar
:
true
};
$idPassword
.
pwstrength
(
options
);
popoverPasswordRules
(
password_check_rules
,
$el
);
}
apps/static/js/pwstrength-bootstrap.js
0 → 100755
View file @
ee35ca36
/*!
* jQuery Password Strength plugin for Twitter Bootstrap
* Version: 2.2.1
*
* Copyright (c) 2008-2013 Tane Piper
* Copyright (c) 2013 Alejandro Blanco
* Dual licensed under the MIT and GPL licenses.
*/
(
function
(
jQuery
)
{
// Source: src/i18n.js
var
i18n
=
{};
(
function
(
i18n
,
i18next
)
{
'use strict'
;
i18n
.
fallback
=
{
"wordMinLength"
:
"Your password is too short"
,
"wordMaxLength"
:
"Your password is too long"
,
"wordInvalidChar"
:
"Your password contains an invalid character"
,
"wordNotEmail"
:
"Do not use your email as your password"
,
"wordSimilarToUsername"
:
"Your password cannot contain your username"
,
"wordTwoCharacterClasses"
:
"Use different character classes"
,
"wordRepetitions"
:
"Too many repetitions"
,
"wordSequences"
:
"Your password contains sequences"
,
"errorList"
:
"Errors:"
,
"veryWeak"
:
"Very Weak"
,
"weak"
:
"Weak"
,
"normal"
:
"Normal"
,
"medium"
:
"Medium"
,
"strong"
:
"Strong"
,
"veryStrong"
:
"Very Strong"
};
i18n
.
t
=
function
(
key
)
{
var
result
=
''
;
// Try to use i18next.com
if
(
i18next
)
{
result
=
i18next
.
t
(
key
);
}
else
{
// Fallback to english
result
=
i18n
.
fallback
[
key
];
}
return
result
===
key
?
''
:
result
;
};
}(
i18n
,
window
.
i18next
));
// Source: src/rules.js
var
rulesEngine
=
{};
try
{
if
(
!
jQuery
&&
module
&&
module
.
exports
)
{
var
jQuery
=
require
(
"jquery"
),
jsdom
=
require
(
"jsdom"
).
jsdom
;
jQuery
=
jQuery
(
jsdom
().
defaultView
);
}
}
catch
(
ignore
)
{}
(
function
(
$
,
rulesEngine
)
{
"use strict"
;
var
validation
=
{};
rulesEngine
.
forbiddenSequences
=
[
"0123456789"
,
"abcdefghijklmnopqrstuvwxyz"
,
"qwertyuiop"
,
"asdfghjkl"
,
"zxcvbnm"
,
"!@#$%^&*()_+"
];
validation
.
wordNotEmail
=
function
(
options
,
word
,
score
)
{
if
(
word
.
match
(
/^
([\w\!\#
$
\%\&\'\*\+\-\/\=\?\^\`
{
\|\}\~]
+
\.)
*
[\w\!\#
$
\%\&\'\*\+\-\/\=\?\^\`
{
\|\}\~]
+@
((((([
a-z0-9
]{1}[
a-z0-9
\-]{0,62}[
a-z0-9
]{1})
|
[
a-z
])\.)
+
[
a-z
]{2,6})
|
(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)
$/i
))
{
return
score
;
}
return
0
;
};
validation
.
wordMinLength
=
function
(
options
,
word
,
score
)
{
var
wordlen
=
word
.
length
,
lenScore
=
Math
.
pow
(
wordlen
,
options
.
rules
.
raisePower
);
if
(
wordlen
<
options
.
common
.
minChar
)
{
lenScore
=
(
lenScore
+
score
);
}
return
lenScore
;
};
validation
.
wordMaxLength
=
function
(
options
,
word
,
score
)
{
var
wordlen
=
word
.
length
,
lenScore
=
Math
.
pow
(
wordlen
,
options
.
rules
.
raisePower
);
if
(
wordlen
>
options
.
common
.
maxChar
)
{
return
score
;
}
return
lenScore
;
};
validation
.
wordInvalidChar
=
function
(
options
,
word
,
score
)
{
if
(
options
.
common
.
invalidCharsRegExp
.
test
(
word
))
{
return
score
;
}
return
0
;
};
validation
.
wordMinLengthStaticScore
=
function
(
options
,
word
,
score
)
{
return
word
.
length
<
options
.
common
.
minChar
?
0
:
score
;
};
validation
.
wordMaxLengthStaticScore
=
function
(
options
,
word
,
score
)
{
return
word
.
length
>
options
.
common
.
maxChar
?
0
:
score
;
};
validation
.
wordSimilarToUsername
=
function
(
options
,
word
,
score
)
{
var
username
=
$
(
options
.
common
.
usernameField
).
val
();
if
(
username
&&
word
.
toLowerCase
().
match
(
username
.
replace
(
/
[\-\[\]\/\{\}\(\)\*\+\=\?\:\.\\\^\$\|\!\,]
/g
,
"
\\
$&"
).
toLowerCase
()))
{
return
score
;
}
return
0
;
};
validation
.
wordTwoCharacterClasses
=
function
(
options
,
word
,
score
)
{
if
(
word
.
match
(
/
([
a-z
]
.*
[
A-Z
])
|
([
A-Z
]
.*
[
a-z
])
/
)
||
(
word
.
match
(
/
([
a-zA-Z
])
/
)
&&
word
.
match
(
/
([
0-9
])
/
))
||
(
word
.
match
(
/
(
.
[
!,@,#,$,%,
\^
,&,*,?,_,~
])
/
)
&&
word
.
match
(
/
[
a-zA-Z0-9_
]
/
)))
{
return
score
;
}
return
0
;
};
validation
.
wordRepetitions
=
function
(
options
,
word
,
score
)
{
if
(
word
.
match
(
/
(
.
)\1\1
/
))
{
return
score
;
}
return
0
;
};
validation
.
wordSequences
=
function
(
options
,
word
,
score
)
{
var
found
=
false
,
j
;
if
(
word
.
length
>
2
)
{
$
.
each
(
rulesEngine
.
forbiddenSequences
,
function
(
idx
,
seq
)
{
if
(
found
)
{
return
;
}
var
sequences
=
[
seq
,
seq
.
split
(
''
).
reverse
().
join
(
''
)];
$
.
each
(
sequences
,
function
(
idx
,
sequence
)
{
for
(
j
=
0
;
j
<
(
word
.
length
-
2
);
j
+=
1
)
{
// iterate the word trough a sliding window of size 3:
if
(
sequence
.
indexOf
(
word
.
toLowerCase
().
substring
(
j
,
j
+
3
))
>
-
1
)
{
found
=
true
;
}
}
});
});
if
(
found
)
{
return
score
;
}
}
return
0
;
};
validation
.
wordLowercase
=
function
(
options
,
word
,
score
)
{
return
word
.
match
(
/
[
a-z
]
/
)
&&
score
;
};
validation
.
wordUppercase
=
function
(
options
,
word
,
score
)
{
return
word
.
match
(
/
[
A-Z
]
/
)
&&
score
;
};
validation
.
wordOneNumber
=
function
(
options
,
word
,
score
)
{
return
word
.
match
(
/
\d
+/
)
&&
score
;
};
validation
.
wordThreeNumbers
=
function
(
options
,
word
,
score
)
{
return
word
.
match
(
/
(
.*
[
0-9
]
.*
[
0-9
]
.*
[
0-9
])
/
)
&&
score
;
};
validation
.
wordOneSpecialChar
=
function
(
options
,
word
,
score
)
{
return
word
.
match
(
/
[
!,@,#,$,%,
\^
,&,*,?,_,~
]
/
)
&&
score
;
};
validation
.
wordTwoSpecialChar
=
function
(
options
,
word
,
score
)
{
return
word
.
match
(
/
(
.*
[
!,@,#,$,%,
\^
,&,*,?,_,~
]
.*
[
!,@,#,$,%,
\^
,&,*,?,_,~
])
/
)
&&
score
;
};
validation
.
wordUpperLowerCombo
=
function
(
options
,
word
,
score
)
{
return
word
.
match
(
/
([
a-z
]
.*
[
A-Z
])
|
([
A-Z
]
.*
[
a-z
])
/
)
&&
score
;
};
validation
.
wordLetterNumberCombo
=
function
(
options
,
word
,
score
)
{
return
word
.
match
(
/
([
a-zA-Z
])
/
)
&&
word
.
match
(
/
([
0-9
])
/
)
&&
score
;
};
validation
.
wordLetterNumberCharCombo
=
function
(
options
,
word
,
score
)
{
return
word
.
match
(
/
([
a-zA-Z0-9
]
.*
[
!,@,#,$,%,
\^
,&,*,?,_,~
])
|
([
!,@,#,$,%,
\^
,&,*,?,_,~
]
.*
[
a-zA-Z0-9
])
/
)
&&
score
;
};
validation
.
wordIsACommonPassword
=
function
(
options
,
word
,
score
)
{
if
(
$
.
inArray
(
word
,
options
.
rules
.
commonPasswords
)
>=
0
)
{
return
score
;
}
return
0
;
};
rulesEngine
.
validation
=
validation
;
rulesEngine
.
executeRules
=
function
(
options
,
word
)
{
var
totalScore
=
0
;
$
.
each
(
options
.
rules
.
activated
,
function
(
rule
,
active
)
{
if
(
active
)
{
var
score
=
options
.
rules
.
scores
[
rule
],
funct
=
rulesEngine
.
validation
[
rule
],
result
,
errorMessage
;
if
(
!
$
.
isFunction
(
funct
))
{
funct
=
options
.
rules
.
extra
[
rule
];
}
if
(
$
.
isFunction
(
funct
))
{
result
=
funct
(
options
,
word
,
score
);
if
(
result
)
{
totalScore
+=
result
;
}
if
(
result
<
0
||
(
!
$
.
isNumeric
(
result
)
&&
!
result
))
{
errorMessage
=
options
.
ui
.
spanError
(
options
,
rule
);
if
(
errorMessage
.
length
>
0
)
{
options
.
instances
.
errors
.
push
(
errorMessage
);
}
}
}
}
});
return
totalScore
;
};
}(
jQuery
,
rulesEngine
));
try
{
if
(
module
&&
module
.
exports
)
{
module
.
exports
=
rulesEngine
;
}
}
catch
(
ignore
)
{}
// Source: src/options.js
var
defaultOptions
=
{};
defaultOptions
.
common
=
{};
defaultOptions
.
common
.
minChar
=
6
;
defaultOptions
.
common
.
maxChar
=
20
;
defaultOptions
.
common
.
usernameField
=
"#username"
;
defaultOptions
.
common
.
invalidCharsRegExp
=
new
RegExp
(
/
[\s
,'"
]
/
);
defaultOptions
.
common
.
userInputs
=
[
// Selectors for input fields with user input
];
defaultOptions
.
common
.
onLoad
=
undefined
;
defaultOptions
.
common
.
onKeyUp
=
undefined
;
defaultOptions
.
common
.
onScore
=
undefined
;
defaultOptions
.
common
.
zxcvbn
=
false
;
defaultOptions
.
common
.
zxcvbnTerms
=
[
// List of disrecommended words
];
defaultOptions
.
common
.
events
=
[
"keyup"
,
"change"
,
"paste"
];
defaultOptions
.
common
.
debug
=
false
;
defaultOptions
.
rules
=
{};
defaultOptions
.
rules
.
extra
=
{};
defaultOptions
.
rules
.
scores
=
{
wordNotEmail
:
-
100
,
wordMinLength
:
-
50
,
wordMaxLength
:
-
50
,
wordInvalidChar
:
-
100
,
wordSimilarToUsername
:
-
100
,
wordSequences
:
-
20
,
wordTwoCharacterClasses
:
2
,
wordRepetitions
:
-
25
,
wordLowercase
:
1
,
wordUppercase
:
3
,
wordOneNumber
:
3
,
wordThreeNumbers
:
5
,
wordOneSpecialChar
:
3
,
wordTwoSpecialChar
:
5
,
wordUpperLowerCombo
:
2
,
wordLetterNumberCombo
:
2
,
wordLetterNumberCharCombo
:
2
,
wordIsACommonPassword
:
-
100
};
defaultOptions
.
rules
.
activated
=
{
wordNotEmail
:
true
,
wordMinLength
:
true
,
wordMaxLength
:
false
,
wordInvalidChar
:
false
,
wordSimilarToUsername
:
true
,
wordSequences
:
true
,
wordTwoCharacterClasses
:
true
,
wordRepetitions
:
true
,
wordLowercase
:
true
,
wordUppercase
:
true
,
wordOneNumber
:
true
,
wordThreeNumbers
:
true
,
wordOneSpecialChar
:
true
,
wordTwoSpecialChar
:
true
,
wordUpperLowerCombo
:
true
,
wordLetterNumberCombo
:
true
,
wordLetterNumberCharCombo
:
true
,
wordIsACommonPassword
:
true
};
defaultOptions
.
rules
.
raisePower
=
1.4
;
// List taken from https://github.com/danielmiessler/SecLists (MIT License)
defaultOptions
.
rules
.
commonPasswords
=
[
'123456'
,
'password'
,
'12345678'
,
'qwerty'
,
'123456789'
,
'12345'
,
'1234'
,
'111111'
,
'1234567'
,
'dragon'
,
'123123'
,
'baseball'
,
'abc123'
,
'football'
,
'monkey'
,
'letmein'
,
'696969'
,
'shadow'
,
'master'
,
'666666'
,
'qwertyuiop'
,
'123321'
,
'mustang'
,
'1234567890'
,
'michael'
,
'654321'
,
'pussy'
,
'superman'
,
'1qaz2wsx'
,
'7777777'
,
'fuckyou'
,
'121212'
,
'000000'
,
'qazwsx'
,
'123qwe'
,
'killer'
,
'trustno1'
,
'jordan'
,
'jennifer'
,
'zxcvbnm'
,
'asdfgh'
,
'hunter'
,
'buster'
,
'soccer'
,
'harley'
,
'batman'
,
'andrew'
,
'tigger'
,
'sunshine'
,
'iloveyou'
,
'fuckme'
,
'2000'
,
'charlie'
,
'robert'
,
'thomas'
,
'hockey'
,
'ranger'
,
'daniel'
,
'starwars'
,
'klaster'
,
'112233'
,
'george'
,
'asshole'
,
'computer'
,
'michelle'
,
'jessica'
,
'pepper'
,
'1111'
,
'zxcvbn'
,
'555555'
,
'11111111'
,
'131313'
,
'freedom'
,
'777777'
,
'pass'
,
'fuck'
,
'maggie'
,
'159753'
,
'aaaaaa'
,
'ginger'
,
'princess'
,
'joshua'
,
'cheese'
,
'amanda'
,
'summer'
,
'love'
,
'ashley'
,
'6969'
,
'nicole'
,
'chelsea'
,
'biteme'
,
'matthew'
,
'access'
,
'yankees'
,
'987654321'
,
'dallas'
,
'austin'
,
'thunder'
,
'taylor'
,
'matrix'
];
defaultOptions
.
ui
=
{};
defaultOptions
.
ui
.
bootstrap2
=
false
;
defaultOptions
.
ui
.
bootstrap4
=
false
;
defaultOptions
.
ui
.
colorClasses
=
[
"danger"
,
"danger"
,
"danger"
,
"warning"
,
"warning"
,
"success"
];
defaultOptions
.
ui
.
showProgressBar
=
true
;
defaultOptions
.
ui
.
progressBarEmptyPercentage
=
1
;
defaultOptions
.
ui
.
progressBarMinPercentage
=
1
;
defaultOptions
.
ui
.
progressExtraCssClasses
=
''
;
defaultOptions
.
ui
.
progressBarExtraCssClasses
=
''
;
defaultOptions
.
ui
.
showPopover
=
false
;
defaultOptions
.
ui
.
popoverPlacement
=
"bottom"
;
defaultOptions
.
ui
.
showStatus
=
false
;
defaultOptions
.
ui
.
spanError
=
function
(
options
,
key
)
{
"use strict"
;
var
text
=
options
.
i18n
.
t
(
key
);
if
(
!
text
)
{
return
''
;
}
return
'<span style="color: #d52929">'
+
text
+
'</span>'
;
};
defaultOptions
.
ui
.
popoverError
=
function
(
options
)
{
"use strict"
;
var
errors
=
options
.
instances
.
errors
,
errorsTitle
=
options
.
i18n
.
t
(
"errorList"
),
message
=
"<div>"
+
errorsTitle
+
"<ul class='error-list' style='margin-bottom: 0;'>"
;
jQuery
.
each
(
errors
,
function
(
idx
,
err
)
{
message
+=
"<li>"
+
err
+
"</li>"
;
});
message
+=
"</ul></div>"
;
return
message
;
};
defaultOptions
.
ui
.
showVerdicts
=
true
;
defaultOptions
.
ui
.
showVerdictsInsideProgressBar
=
false
;
defaultOptions
.
ui
.
useVerdictCssClass
=
false
;
defaultOptions
.
ui
.
showErrors
=
false
;
defaultOptions
.
ui
.
showScore
=
false
;
defaultOptions
.
ui
.
container
=
undefined
;
defaultOptions
.
ui
.
viewports
=
{
progress
:
undefined
,
verdict
:
undefined
,
errors
:
undefined
,
score
:
undefined
};
defaultOptions
.
ui
.
scores
=
[
0
,
14
,
26
,
38
,
50
];
defaultOptions
.
i18n
=
{};
defaultOptions
.
i18n
.
t
=
i18n
.
t
;
// Source: src/ui.js
var
ui
=
{};
(
function
(
$
,
ui
)
{
"use strict"
;
var
statusClasses
=
[
"error"
,
"warning"
,
"success"
],
verdictKeys
=
[
"veryWeak"
,
"weak"
,
"normal"
,
"medium"
,
"strong"
,
"veryStrong"
];
ui
.
getContainer
=
function
(
options
,
$el
)
{
var
$container
;
$container
=
$
(
options
.
ui
.
container
);
if
(
!
(
$container
&&
$container
.
length
===
1
))
{
$container
=
$el
.
parent
();
}
return
$container
;
};
ui
.
findElement
=
function
(
$container
,
viewport
,
cssSelector
)
{
if
(
viewport
)
{
return
$container
.
find
(
viewport
).
find
(
cssSelector
);
}
return
$container
.
find
(
cssSelector
);
};
ui
.
getUIElements
=
function
(
options
,
$el
)
{
var
$container
,
result
;
if
(
options
.
instances
.
viewports
)
{
return
options
.
instances
.
viewports
;
}
$container
=
ui
.
getContainer
(
options
,
$el
);
result
=
{};
result
.
$progressbar
=
ui
.
findElement
(
$container
,
options
.
ui
.
viewports
.
progress
,
"div.progress"
);
if
(
options
.
ui
.
showVerdictsInsideProgressBar
)
{
result
.
$verdict
=
result
.
$progressbar
.
find
(
"span.password-verdict"
);
}
if
(
!
options
.
ui
.
showPopover
)
{
if
(
!
options
.
ui
.
showVerdictsInsideProgressBar
)
{
result
.
$verdict
=
ui
.
findElement
(
$container
,
options
.
ui
.
viewports
.
verdict
,
"span.password-verdict"
);
}
result
.
$errors
=
ui
.
findElement
(
$container
,
options
.
ui
.
viewports
.
errors
,
"ul.error-list"
);
}
result
.
$score
=
ui
.
findElement
(
$container
,
options
.
ui
.
viewports
.
score
,
"span.password-score"
);
options
.
instances
.
viewports
=
result
;
return
result
;
};
ui
.
initProgressBar
=
function
(
options
,
$el
)
{
var
$container
=
ui
.
getContainer
(
options
,
$el
),
progressbar
=
"<div class='progress "
;
if
(
options
.
ui
.
bootstrap2
)
{
// Boostrap 2
progressbar
+=
options
.
ui
.
progressBarExtraCssClasses
+
"'><div class='"
;
}
else
{
// Bootstrap 3 & 4
progressbar
+=
options
.
ui
.
progressExtraCssClasses
+
"'><div class='"
+
options
.
ui
.
progressBarExtraCssClasses
+
" progress-"
;
}
progressbar
+=
"bar'>"
;
if
(
options
.
ui
.
showVerdictsInsideProgressBar
)
{
progressbar
+=
"<span class='password-verdict'></span>"
;
}
progressbar
+=
"</div></div>"
;
if
(
options
.
ui
.
viewports
.
progress
)
{
$container
.
find
(
options
.
ui
.
viewports
.
progress
).
append
(
progressbar
);
}
else
{
$
(
progressbar
).
insertAfter
(
$el
);
}
};
ui
.
initHelper
=
function
(
options
,
$el
,
html
,
viewport
)
{
var
$container
=
ui
.
getContainer
(
options
,
$el
);
if
(
viewport
)
{
$container
.
find
(
viewport
).
append
(
html
);
}
else
{
$
(
html
).
insertAfter
(
$el
);
}
};
ui
.
initVerdict
=
function
(
options
,
$el
)
{
ui
.
initHelper
(
options
,
$el
,
"<span class='password-verdict'></span>"
,
options
.
ui
.
viewports
.
verdict
);
};
ui
.
initErrorList
=
function
(
options
,
$el
)
{
ui
.
initHelper
(
options
,
$el
,
"<ul class='error-list'></ul >"
,
options
.
ui
.
viewports
.
errors
);
};
ui
.
initScore
=
function
(
options
,
$el
)
{
ui
.
initHelper
(
options
,
$el
,
"<span class='password-score'></span>"
,
options
.
ui
.
viewports
.
score
);
};
ui
.
initPopover
=
function
(
options
,
$el
)
{
$el
.
popover
(
"destroy"
);
$el
.
popover
({
html
:
true
,
placement
:
options
.
ui
.
popoverPlacement
,
trigger
:
"manual"
,
content
:
" "
});
};
ui
.
initUI
=
function
(
options
,
$el
)
{
if
(
options
.
ui
.
showPopover
)
{
ui
.
initPopover
(
options
,
$el
);
}
else
{
if
(
options
.
ui
.
showErrors
)
{
ui
.
initErrorList
(
options
,
$el
);
}
if
(
options
.
ui
.
showVerdicts
&&
!
options
.
ui
.
showVerdictsInsideProgressBar
)
{
ui
.
initVerdict
(
options
,
$el
);
}
}
if
(
options
.
ui
.
showProgressBar
)
{
ui
.
initProgressBar
(
options
,
$el
);
}
if
(
options
.
ui
.
showScore
)
{
ui
.
initScore
(
options
,
$el
);
}
};
ui
.
updateProgressBar
=
function
(
options
,
$el
,
cssClass
,
percentage
)
{
var
$progressbar
=
ui
.
getUIElements
(
options
,
$el
).
$progressbar
,
$bar
=
$progressbar
.
find
(
".progress-bar"
),
cssPrefix
=
"progress-"
;
if
(
options
.
ui
.
bootstrap2
)
{
$bar
=
$progressbar
.
find
(
".bar"
);
cssPrefix
=
""
;
}
$
.
each
(
options
.
ui
.
colorClasses
,
function
(
idx
,
value
)
{
if
(
options
.
ui
.
bootstrap4
)
{
$bar
.
removeClass
(
"bg-"
+
value
);
}
else
{
$bar
.
removeClass
(
cssPrefix
+
"bar-"
+
value
);
}
});
if
(
options
.
ui
.
bootstrap4
)
{
$bar
.
addClass
(
"bg-"
+
options
.
ui
.
colorClasses
[
cssClass
]);
}
else
{
$bar
.
addClass
(
cssPrefix
+
"bar-"
+
options
.
ui
.
colorClasses
[
cssClass
]);
}
$bar
.
css
(
"width"
,
percentage
+
'%'
);
};
ui
.
updateVerdict
=
function
(
options
,
$el
,
cssClass
,
text
)
{
var
$verdict
=
ui
.
getUIElements
(
options
,
$el
).
$verdict
;
$verdict
.
removeClass
(
options
.
ui
.
colorClasses
.
join
(
' '
));
if
(
cssClass
>
-
1
)
{
$verdict
.
addClass
(
options
.
ui
.
colorClasses
[
cssClass
]);
}
if
(
options
.
ui
.
showVerdictsInsideProgressBar
)
{
$verdict
.
css
(
'white-space'
,
'nowrap'
);
}
$verdict
.
html
(
text
);
};
ui
.
updateErrors
=
function
(
options
,
$el
,
remove
)
{
var
$errors
=
ui
.
getUIElements
(
options
,
$el
).
$errors
,
html
=
""
;
if
(
!
remove
)
{
$
.
each
(
options
.
instances
.
errors
,
function
(
idx
,
err
)
{
html
+=
"<li style='list-style-type:none;'>"
+
err
+
"</li>"
;
});
}
$errors
.
html
(
html
);
};
ui
.
updateScore
=
function
(
options
,
$el
,
score
,
remove
)
{
var
$score
=
ui
.
getUIElements
(
options
,
$el
).
$score
,
html
=
""
;
if
(
!
remove
)
{
html
=
score
.
toFixed
(
2
);
}
$score
.
html
(
html
);
};
ui
.
updatePopover
=
function
(
options
,
$el
,
verdictText
,
remove
)
{
var
popover
=
$el
.
data
(
"bs.popover"
),
html
=
""
,
hide
=
true
;
if
(
options
.
ui
.
showVerdicts
&&
!
options
.
ui
.
showVerdictsInsideProgressBar
&&
verdictText
.
length
>
0
)
{
html
=
"<h5><span class='password-verdict'>"
+
verdictText
+
"</span></h5>"
;
hide
=
false
;
}
if
(
options
.
ui
.
showErrors
)
{
if
(
options
.
instances
.
errors
.
length
>
0
)
{
hide
=
false
;
}
html
+=
options
.
ui
.
popoverError
(
options
);
}
if
(
hide
||
remove
)
{
$el
.
popover
(
"hide"
);
return
;
}
if
(
options
.
ui
.
bootstrap2
)
{
popover
=
$el
.
data
(
"popover"
);
}
if
(
popover
.
$arrow
&&
popover
.
$arrow
.
parents
(
"body"
).
length
>
0
)
{
$el
.
find
(
"+ .popover .popover-content"
).
html
(
html
);
}
else
{
// It's hidden
popover
.
options
.
content
=
html
;
$el
.
popover
(
"show"
);
}
};
ui
.
updateFieldStatus
=
function
(
options
,
$el
,
cssClass
,
remove
)
{
var
targetClass
=
options
.
ui
.
bootstrap2
?
".control-group"
:
".form-group"
,
$container
=
$el
.
parents
(
targetClass
).
first
();
$
.
each
(
statusClasses
,
function
(
idx
,
css
)
{
if
(
!
options
.
ui
.
bootstrap2
)
{
css
=
"has-"
+
css
;
}
$container
.
removeClass
(
css
);
});
if
(
remove
)
{
return
;
}
cssClass
=
statusClasses
[
Math
.
floor
(
cssClass
/
2
)];
if
(
!
options
.
ui
.
bootstrap2
)
{
cssClass
=
"has-"
+
cssClass
;
}
$container
.
addClass
(
cssClass
);
};
ui
.
percentage
=
function
(
options
,
score
,
maximun
)
{
var
result
=
Math
.
floor
(
100
*
score
/
maximun
),
min
=
options
.
ui
.
progressBarMinPercentage
;
result
=
result
<=
min
?
min
:
result
;
result
=
result
>
100
?
100
:
result
;
return
result
;
};
ui
.
getVerdictAndCssClass
=
function
(
options
,
score
)
{
var
level
,
verdict
;
if
(
score
===
undefined
)
{
return
[
''
,
0
];
}
if
(
score
<=
options
.
ui
.
scores
[
0
])
{
level
=
0
;
}
else
if
(
score
<
options
.
ui
.
scores
[
1
])
{
level
=
1
;
}
else
if
(
score
<
options
.
ui
.
scores
[
2
])
{
level
=
2
;
}
else
if
(
score
<
options
.
ui
.
scores
[
3
])
{
level
=
3
;
}
else
if
(
score
<
options
.
ui
.
scores
[
4
])
{
level
=
4
;
}
else
{
level
=
5
;
}
verdict
=
verdictKeys
[
level
];
return
[
options
.
i18n
.
t
(
verdict
),
level
];
};
ui
.
updateUI
=
function
(
options
,
$el
,
score
)
{
var
cssClass
,
barPercentage
,
verdictText
,
verdictCssClass
;
cssClass
=
ui
.
getVerdictAndCssClass
(
options
,
score
);
verdictText
=
score
===
0
?
''
:
cssClass
[
0
];
cssClass
=
cssClass
[
1
];
verdictCssClass
=
options
.
ui
.
useVerdictCssClass
?
cssClass
:
-
1
;
if
(
options
.
ui
.
showProgressBar
)
{
if
(
score
===
undefined
)
{
barPercentage
=
options
.
ui
.
progressBarEmptyPercentage
;
}
else
{
barPercentage
=
ui
.
percentage
(
options
,
score
,
options
.
ui
.
scores
[
4
]);
}
ui
.
updateProgressBar
(
options
,
$el
,
cssClass
,
barPercentage
);
if
(
options
.
ui
.
showVerdictsInsideProgressBar
)
{
ui
.
updateVerdict
(
options
,
$el
,
verdictCssClass
,
verdictText
);
}
}
if
(
options
.
ui
.
showStatus
)
{
ui
.
updateFieldStatus
(
options
,
$el
,
cssClass
,
score
===
undefined
);
}
if
(
options
.
ui
.
showPopover
)
{
ui
.
updatePopover
(
options
,
$el
,
verdictText
,
score
===
undefined
);
}
else
{
if
(
options
.
ui
.
showVerdicts
&&
!
options
.
ui
.
showVerdictsInsideProgressBar
)
{
ui
.
updateVerdict
(
options
,
$el
,
verdictCssClass
,
verdictText
);
}
if
(
options
.
ui
.
showErrors
)
{
ui
.
updateErrors
(
options
,
$el
,
score
===
undefined
);
}
}
if
(
options
.
ui
.
showScore
)
{
ui
.
updateScore
(
options
,
$el
,
score
,
score
===
undefined
);
}
};
}(
jQuery
,
ui
));
// Source: src/methods.js
var
methods
=
{};
(
function
(
$
,
methods
)
{
"use strict"
;
var
onKeyUp
,
onPaste
,
applyToAll
;
onKeyUp
=
function
(
event
)
{
var
$el
=
$
(
event
.
target
),
options
=
$el
.
data
(
"pwstrength-bootstrap"
),
word
=
$el
.
val
(),
userInputs
,
verdictText
,
verdictLevel
,
score
;
if
(
options
===
undefined
)
{
return
;
}
options
.
instances
.
errors
=
[];
if
(
word
.
length
===
0
)
{
score
=
undefined
;
}
else
{
if
(
options
.
common
.
zxcvbn
)
{
userInputs
=
[];
$
.
each
(
options
.
common
.
userInputs
.
concat
([
options
.
common
.
usernameField
]),
function
(
idx
,
selector
)
{
var
value
=
$
(
selector
).
val
();
if
(
value
)
{
userInputs
.
push
(
value
);
}
});
userInputs
=
userInputs
.
concat
(
options
.
common
.
zxcvbnTerms
);
score
=
zxcvbn
(
word
,
userInputs
).
guesses
;
score
=
Math
.
log
(
score
)
*
Math
.
LOG2E
;
}
else
{
score
=
rulesEngine
.
executeRules
(
options
,
word
);
}
if
(
$
.
isFunction
(
options
.
common
.
onScore
))
{
score
=
options
.
common
.
onScore
(
options
,
word
,
score
);
}
}
ui
.
updateUI
(
options
,
$el
,
score
);
verdictText
=
ui
.
getVerdictAndCssClass
(
options
,
score
);
verdictLevel
=
verdictText
[
1
];
verdictText
=
verdictText
[
0
];
if
(
options
.
common
.
debug
)
{
console
.
log
(
score
+
' - '
+
verdictText
);
}
if
(
$
.
isFunction
(
options
.
common
.
onKeyUp
))
{
options
.
common
.
onKeyUp
(
event
,
{
score
:
score
,
verdictText
:
verdictText
,
verdictLevel
:
verdictLevel
});
}
};
onPaste
=
function
(
event
)
{
// This handler is necessary because the paste event fires before the
// content is actually in the input, so we cannot read its value right
// away. Therefore, the timeouts.
var
$el
=
$
(
event
.
target
),
word
=
$el
.
val
(),
tries
=
0
,
callback
;
callback
=
function
()
{
var
newWord
=
$el
.
val
();
if
(
newWord
!==
word
)
{
onKeyUp
(
event
);
}
else
if
(
tries
<
3
)
{
tries
+=
1
;
setTimeout
(
callback
,
100
);
}
};
setTimeout
(
callback
,
100
);
};
methods
.
init
=
function
(
settings
)
{
this
.
each
(
function
(
idx
,
el
)
{
// Make it deep extend (first param) so it extends also the
// rules and other inside objects
var
clonedDefaults
=
$
.
extend
(
true
,
{},
defaultOptions
),
localOptions
=
$
.
extend
(
true
,
clonedDefaults
,
settings
),
$el
=
$
(
el
);
localOptions
.
instances
=
{};
$el
.
data
(
"pwstrength-bootstrap"
,
localOptions
);
$
.
each
(
localOptions
.
common
.
events
,
function
(
idx
,
eventName
)
{
var
handler
=
eventName
===
"paste"
?
onPaste
:
onKeyUp
;
$el
.
on
(
eventName
,
handler
);
});
ui
.
initUI
(
localOptions
,
$el
);
$el
.
trigger
(
"keyup"
);
if
(
$
.
isFunction
(
localOptions
.
common
.
onLoad
))
{
localOptions
.
common
.
onLoad
();
}
});
return
this
;
};
methods
.
destroy
=
function
()
{
this
.
each
(
function
(
idx
,
el
)
{
var
$el
=
$
(
el
),
options
=
$el
.
data
(
"pwstrength-bootstrap"
),
elements
=
ui
.
getUIElements
(
options
,
$el
);
elements
.
$progressbar
.
remove
();
elements
.
$verdict
.
remove
();
elements
.
$errors
.
remove
();
$el
.
removeData
(
"pwstrength-bootstrap"
);
});
};
methods
.
forceUpdate
=
function
()
{
this
.
each
(
function
(
idx
,
el
)
{
var
event
=
{
target
:
el
};
onKeyUp
(
event
);
});
};
methods
.
addRule
=
function
(
name
,
method
,
score
,
active
)
{
this
.
each
(
function
(
idx
,
el
)
{
var
options
=
$
(
el
).
data
(
"pwstrength-bootstrap"
);
options
.
rules
.
activated
[
name
]
=
active
;
options
.
rules
.
scores
[
name
]
=
score
;
options
.
rules
.
extra
[
name
]
=
method
;
});
};
applyToAll
=
function
(
rule
,
prop
,
value
)
{
this
.
each
(
function
(
idx
,
el
)
{
$
(
el
).
data
(
"pwstrength-bootstrap"
).
rules
[
prop
][
rule
]
=
value
;
});
};
methods
.
changeScore
=
function
(
rule
,
score
)
{
applyToAll
.
call
(
this
,
rule
,
"scores"
,
score
);
};
methods
.
ruleActive
=
function
(
rule
,
active
)
{
applyToAll
.
call
(
this
,
rule
,
"activated"
,
active
);
};
methods
.
ruleIsMet
=
function
(
rule
)
{
if
(
$
.
isFunction
(
rulesEngine
.
validation
[
rule
]))
{
if
(
rule
===
"wordMinLength"
)
{
rule
=
"wordMinLengthStaticScore"
;
}
else
if
(
rule
===
"wordMaxLength"
)
{
rule
=
"wordMaxLengthStaticScore"
;
}
var
rulesMetCnt
=
0
;
this
.
each
(
function
(
idx
,
el
)
{
var
options
=
$
(
el
).
data
(
"pwstrength-bootstrap"
);
rulesMetCnt
+=
rulesEngine
.
validation
[
rule
](
options
,
$
(
el
).
val
(),
1
);
});
return
(
rulesMetCnt
===
this
.
length
);
}
$
.
error
(
"Rule "
+
rule
+
" does not exist on jQuery.pwstrength-bootstrap.validation"
);
};
$
.
fn
.
pwstrength
=
function
(
method
)
{
var
result
;
if
(
methods
[
method
])
{
result
=
methods
[
method
].
apply
(
this
,
Array
.
prototype
.
slice
.
call
(
arguments
,
1
));
}
else
if
(
typeof
method
===
"object"
||
!
method
)
{
result
=
methods
.
init
.
apply
(
this
,
arguments
);
}
else
{
$
.
error
(
"Method "
+
method
+
" does not exist on jQuery.pwstrength-bootstrap"
);
}
return
result
;
};
}(
jQuery
,
methods
));
}(
jQuery
));
\ No newline at end of file
apps/templates/_base_create_update.html
View file @
ee35ca36
...
...
@@ -5,6 +5,7 @@
{% block custom_head_css_js %}
<link
href=
"{% static 'css/plugins/select2/select2.min.css' %}"
rel=
"stylesheet"
>
<script
src=
"{% static 'js/plugins/select2/select2.full.min.js' %}"
></script>
<script
type=
"text/javascript"
src=
"{% static 'js/pwstrength-bootstrap.js' %}"
></script>
{% block custom_head_css_js_create %} {% endblock %}
{% endblock %}
...
...
apps/users/forms.py
View file @
ee35ca36
...
...
@@ -72,7 +72,7 @@ class UserCreateUpdateForm(forms.ModelForm):
'data-placeholder'
:
_
(
'Join user groups'
)
}
),
'otp_level'
:
forms
.
RadioSelect
()
'otp_level'
:
forms
.
RadioSelect
()
,
}
def
clean_public_key
(
self
):
...
...
apps/users/templates/users/_user.html
View file @
ee35ca36
...
...
@@ -48,6 +48,7 @@
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script
src=
"{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"
></script>
...
...
apps/users/templates/users/reset_password.html
View file @
ee35ca36
...
...
@@ -11,6 +11,7 @@
{% include '_head_css_js.html' %}
<link
href=
"{% static "
css
/
jumpserver
.
css
"
%}"
rel=
"stylesheet"
>
<script
src=
"{% static "
js
/
jumpserver
.
js
"
%}"
></script>
<script
type=
"text/javascript"
src=
"{% static 'js/pwstrength-bootstrap.js' %}"
></script>
</head>
...
...
@@ -49,10 +50,20 @@
<p
class=
"red-fonts"
>
{{ errors }}
</p>
{% endif %}
<div
class=
"form-group"
>
<input
type=
"password"
class=
"form-control"
name=
"password"
placeholder=
"{% trans 'Password' %}"
required=
""
>
<input
type=
"password"
id=
"id_new_password"
class=
"form-control"
name=
"password"
placeholder=
"{% trans 'Password' %}"
required=
""
>
{# 密码popover #}
<div
id=
"container"
>
<div
class=
"popover fade bottom in"
role=
"tooltip"
id=
"popover777"
style=
" display: none; width:260px;"
>
<div
class=
"arrow"
style=
"left: 50%;"
></div>
<h3
class=
"popover-title"
style=
"display: none;"
></h3>
<h4>
{% trans 'Your password must satisfy' %}
</h4><div
id=
"id_password_rules"
style=
"color: #908a8a; margin-left:20px; font-size:15px;"
></div>
<h4
style=
"margin-top: 10px;"
>
{% trans 'Password strength' %}
</h4><div
id=
"id_progress"
></div>
<div
class=
"popover-content"
></div>
</div>
</div>
</div>
<div
class=
"form-group"
>
<input
type=
"password"
class=
"form-control"
name=
"password-confirm"
placeholder=
"{% trans 'Password again' %}"
required=
""
>
<input
type=
"password"
id=
"id_confirm_password"
class=
"form-control"
name=
"password-confirm"
placeholder=
"{% trans 'Password again' %}"
required=
""
>
</div>
<button
type=
"submit"
class=
"btn btn-primary block full-width m-b"
>
{% trans "Setting" %}
</button>
...
...
@@ -79,4 +90,33 @@
</body>
</html>
<script>
$
(
document
).
ready
(
function
()
{
// 密码强度校验
var
el
=
$
(
'#id_password_rules'
),
idPassword
=
$
(
'#id_new_password'
),
idPopover
=
$
(
'#popover777'
),
container
=
$
(
'#container'
),
progress
=
$
(
'#id_progress'
),
password_check_rules
=
{{
password_check_rules
|
safe
}},
minLength
=
{{
min_length
}},
top
=
146
,
left
=
170
;
// 初始化popover
initPopover
(
container
,
progress
,
idPassword
,
el
,
password_check_rules
);
// 监听事件
idPassword
.
on
(
'focus'
,
function
()
{
idPopover
.
css
(
'top'
,
top
);
idPopover
.
css
(
'left'
,
left
);
idPopover
.
css
(
'display'
,
'block'
);
});
idPassword
.
on
(
'blur'
,
function
()
{
idPopover
.
css
(
'display'
,
'none'
);
});
idPassword
.
on
(
'keyup'
,
function
(){
var
password
=
idPassword
.
val
();
checkPasswordRules
(
password
,
minLength
);
})
})
</script>
apps/users/templates/users/user_password_update.html
View file @
ee35ca36
...
...
@@ -7,6 +7,8 @@
<link
href=
"{% static "
css
/
plugins
/
cropper
/
cropper
.
min
.
css
"
%}"
rel=
"stylesheet"
>
<link
href=
"{% static "
css
/
plugins
/
sweetalert
/
sweetalert
.
css
"
%}"
rel=
"stylesheet"
>
<script
src=
"{% static "
js
/
plugins
/
sweetalert
/
sweetalert
.
min
.
js
"
%}"
></script>
<script
type=
"text/javascript"
src=
"{% static 'js/pwstrength-bootstrap.js' %}"
></script>
<script
src=
"{% static "
js
/
jumpserver
.
js
"
%}"
></script>
<style>
.crop
{
...
...
@@ -50,6 +52,16 @@
{% csrf_token %}
{% bootstrap_field form.old_password layout="horizontal" %}
{% bootstrap_field form.new_password layout="horizontal" %}
{# 密码popover #}
<div
id=
"container"
>
<div
class=
"popover fade bottom in"
role=
"tooltip"
id=
"popover777"
style=
" display: none; width:260px;"
>
<div
class=
"arrow"
style=
"left: 50%;"
></div>
<h3
class=
"popover-title"
style=
"display: none;"
></h3>
<h4>
{% trans 'Your password must satisfy' %}
</h4><div
id=
"id_password_rules"
style=
"color: #908a8a; margin-left:20px; font-size:15px;"
></div>
<h4
style=
"margin-top: 10px;"
>
{% trans 'Password strength' %}
</h4><div
id=
"id_progress"
></div>
<div
class=
"popover-content"
></div>
</div>
</div>
{% bootstrap_field form.confirm_password layout="horizontal" %}
<div
class=
"hr-line-dashed"
></div>
...
...
@@ -71,5 +83,34 @@
{% block custom_foot_js %}
<script
src=
"{% static 'js/plugins/cropper/cropper.min.js' %}"
></script>
<script>
$
(
document
).
ready
(
function
()
{
var
el
=
$
(
'#id_password_rules'
),
idPassword
=
$
(
'#id_new_password'
),
idPopover
=
$
(
'#popover777'
),
container
=
$
(
'#container'
),
progress
=
$
(
'#id_progress'
),
password_check_rules
=
{{
password_check_rules
|
safe
}},
minLength
=
{{
min_length
}},
top
=
idPassword
.
offset
().
top
-
$
(
'.navbar'
).
outerHeight
(
true
)
-
$
(
'.page-heading'
).
outerHeight
(
true
)
-
10
+
34
,
left
=
377
;
// 初始化popover
initPopover
(
container
,
progress
,
idPassword
,
el
,
password_check_rules
);
// 监听事件
idPassword
.
on
(
'focus'
,
function
()
{
idPopover
.
css
(
'top'
,
top
);
idPopover
.
css
(
'left'
,
left
);
idPopover
.
css
(
'display'
,
'block'
);
});
idPassword
.
on
(
'blur'
,
function
()
{
idPopover
.
css
(
'display'
,
'none'
);
});
idPassword
.
on
(
'keyup'
,
function
(){
var
password
=
idPassword
.
val
();
checkPasswordRules
(
password
,
minLength
);
});
})
</script>
{% endblock %}
apps/users/templates/users/user_profile.html
View file @
ee35ca36
...
...
@@ -91,6 +91,9 @@
{% else %}
{% trans 'Disable' %}
{% endif %}
{% if mfa_setting %}
( {% trans 'Administrator Settings force MFA login' %} )
{% endif %}
</td>
</tr>
<tr>
...
...
@@ -152,7 +155,7 @@
<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 %}
{% if request.user.otp_force_enabled
or mfa_setting
%}
"
disabled
>
{% trans 'Disable' %}
{% else %}
{% url 'users:user-otp-disable-authentication' %}
...
...
apps/users/templates/users/user_update.html
View file @
ee35ca36
...
...
@@ -4,5 +4,50 @@
{% block user_template_title %}{% trans "Update user" %}{% endblock %}
{% block password %}
{% bootstrap_field form.password layout="horizontal" %}
{# 密码popover #}
<div
id=
"container"
>
<div
class=
"popover fade bottom in"
role=
"tooltip"
id=
"popover777"
style=
" display: none; width:260px;"
>
<div
class=
"arrow"
style=
"left: 50%;"
></div>
<h3
class=
"popover-title"
style=
"display: none;"
></h3>
<h4>
{% trans 'Your password must satisfy' %}
</h4><div
id=
"id_password_rules"
style=
"color: #908a8a; margin-left:20px; font-size:15px;"
></div>
<h4
style=
"margin-top: 10px;"
>
{% trans 'Password strength' %}
</h4><div
id=
"id_progress"
></div>
<div
class=
"popover-content"
></div>
</div>
</div>
{% bootstrap_field form.public_key layout="horizontal" %}
{% endblock %}
{% block custom_foot_js %}
{{ block.super }}
<script>
$
(
document
).
ready
(
function
(){
var
el
=
$
(
'#id_password_rules'
),
idPassword
=
$
(
'#id_password'
),
idPopover
=
$
(
'#popover777'
),
container
=
$
(
'#container'
),
progress
=
$
(
'#id_progress'
),
password_check_rules
=
{{
password_check_rules
|
safe
}},
minLength
=
{{
min_length
}},
top
=
idPassword
.
offset
().
top
-
$
(
'.navbar'
).
outerHeight
(
true
)
-
$
(
'.page-heading'
).
outerHeight
(
true
)
-
10
+
34
,
left
=
377
;
// 初始化popover
initPopover
(
container
,
progress
,
idPassword
,
el
,
password_check_rules
);
// 监听事件
idPassword
.
on
(
'focus'
,
function
()
{
idPopover
.
css
(
'top'
,
top
);
idPopover
.
css
(
'left'
,
left
);
idPopover
.
css
(
'display'
,
'block'
);
});
idPassword
.
on
(
'blur'
,
function
()
{
idPopover
.
css
(
'display'
,
'none'
);
});
idPassword
.
on
(
'keyup'
,
function
(){
var
password
=
idPassword
.
val
();
checkPasswordRules
(
password
,
minLength
);
});
})
</script>
{% endblock %}
apps/users/utils.py
View file @
ee35ca36
...
...
@@ -2,6 +2,7 @@
#
from
__future__
import
unicode_literals
import
os
import
re
import
pyotp
import
base64
import
logging
...
...
@@ -18,8 +19,11 @@ from django.core.cache import cache
from
common.tasks
import
send_mail_async
from
common.utils
import
reverse
,
get_object_or_none
from
common.models
import
Setting
from
common.forms
import
SecuritySettingForm
from
.models
import
User
,
LoginLog
logger
=
logging
.
getLogger
(
'jumpserver'
)
...
...
@@ -271,3 +275,60 @@ def generate_otp_uri(request, issuer="Jumpserver"):
def
check_otp_code
(
otp_secret_key
,
otp_code
):
totp
=
pyotp
.
TOTP
(
otp_secret_key
)
return
totp
.
verify
(
otp_code
)
def
get_password_check_rules
():
check_rules
=
[]
min_length
=
settings
.
DEFAULT_PASSWORD_MIN_LENGTH
min_name
=
'SECURITY_PASSWORD_MIN_LENGTH'
base_filed
=
SecuritySettingForm
.
base_fields
password_setting
=
Setting
.
objects
.
filter
(
name__startswith
=
'SECURITY_PASSWORD'
)
if
not
password_setting
:
# 用户还没有设置过密码校验规则
label
=
base_filed
.
get
(
min_name
)
.
label
label
+=
' '
+
str
(
min_length
)
+
_
(
'Bit'
)
id
=
'rule_'
+
min_name
rules
=
{
'id'
:
id
,
'label'
:
label
}
check_rules
.
append
(
rules
)
for
setting
in
password_setting
:
if
setting
.
cleaned_value
:
id
=
'rule_'
+
setting
.
name
label
=
base_filed
.
get
(
setting
.
name
)
.
label
if
setting
.
name
==
min_name
:
label
+=
str
(
setting
.
cleaned_value
)
+
_
(
'Bit'
)
min_length
=
setting
.
cleaned_value
rules
=
{
'id'
:
id
,
'label'
:
label
}
check_rules
.
append
(
rules
)
return
check_rules
,
min_length
def
check_password_rules
(
password
):
min_field_name
=
'SECURITY_PASSWORD_MIN_LENGTH'
upper_field_name
=
'SECURITY_PASSWORD_UPPER_CASE'
lower_field_name
=
'SECURITY_PASSWORD_LOWER_CASE'
number_field_name
=
'SECURITY_PASSWORD_NUMBER'
special_field_name
=
'SECURITY_PASSWORD_SPECIAL_CHAR'
min_length_setting
=
Setting
.
objects
.
filter
(
name
=
min_field_name
)
.
first
()
min_length
=
min_length_setting
.
value
if
min_length_setting
else
settings
.
DEFAULT_PASSWORD_MIN_LENGTH
password_setting
=
Setting
.
objects
.
filter
(
name__startswith
=
'SECURITY_PASSWORD'
)
if
not
password_setting
:
pattern
=
r"^.{"
+
str
(
min_length
)
+
",}$"
else
:
pattern
=
r"^"
for
setting
in
password_setting
:
if
setting
.
cleaned_value
and
setting
.
name
==
upper_field_name
:
pattern
+=
'(?=.*[A-Z])'
elif
setting
.
cleaned_value
and
setting
.
name
==
lower_field_name
:
pattern
+=
'(?=.*[a-z])'
elif
setting
.
cleaned_value
and
setting
.
name
==
number_field_name
:
pattern
+=
'(?=.*
\
d)'
elif
setting
.
cleaned_value
and
setting
.
name
==
special_field_name
:
pattern
+=
'(?=.*[`~!@#
\
$
%
\
^&
\
*
\
(
\
)-=_
\
+
\
[
\
]
\
{
\
}
\
|;:
\'
",
\
.<>
\
/
\
?])'
pattern
+=
'[a-zA-Z
\
d`~!@#
\
$
%
\
^&
\
*
\
(
\
)-=_
\
+
\
[
\
]
\
{
\
}
\
|;:
\'
",
\
.<>
\
/
\
?]'
match_obj
=
re
.
match
(
pattern
,
password
)
return
bool
(
match_obj
)
apps/users/views/login.py
View file @
ee35ca36
...
...
@@ -23,9 +23,10 @@ from django.conf import settings
from
common.utils
import
get_object_or_none
from
common.mixins
import
DatetimeSearchMixin
,
AdminUserRequiredMixin
from
common.models
import
Setting
from
..models
import
User
,
LoginLog
from
..utils
import
send_reset_password_mail
,
check_otp_code
,
get_login_ip
,
redirect_user_first_login_or_index
,
\
get_user_or_tmp_user
,
set_tmp_user_to_cache
get_user_or_tmp_user
,
set_tmp_user_to_cache
,
get_password_check_rules
,
check_password_rules
from
..tasks
import
write_login_log_async
from
..
import
forms
...
...
@@ -81,17 +82,24 @@ class UserLoginView(FormView):
def
get_success_url
(
self
):
user
=
get_user_or_tmp_user
(
self
.
request
)
if
user
.
otp_enabled
and
user
.
otp_secret_key
:
# 1,2 & T
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
)
mfa_setting
=
Setting
.
objects
.
filter
(
name
=
'SECURITY_MFA_AUTH'
)
.
first
()
if
mfa_setting
and
mfa_setting
.
cleaned_value
:
if
user
.
otp_enabled
and
user
.
otp_secret_key
:
return
reverse
(
'users:login-otp'
)
else
:
return
reverse
(
'users:user-otp-enable-authentication'
)
else
:
if
user
.
otp_enabled
and
user
.
otp_secret_key
:
# 1,2 & T
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
):
context
=
{
...
...
@@ -211,6 +219,10 @@ class UserResetPasswordView(TemplateView):
token
=
request
.
GET
.
get
(
'token'
)
user
=
User
.
validate_reset_token
(
token
)
check_rules
,
min_length
=
get_password_check_rules
()
password_rules
=
{
'password_check_rules'
:
check_rules
,
'min_length'
:
min_length
}
kwargs
.
update
(
password_rules
)
if
not
user
:
kwargs
.
update
({
'errors'
:
_
(
'Token invalid or expired'
)})
return
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
...
...
@@ -227,6 +239,13 @@ class UserResetPasswordView(TemplateView):
if
not
user
:
return
self
.
get
(
request
,
errors
=
_
(
'Token invalid or expired'
))
is_ok
=
check_password_rules
(
password
)
if
not
is_ok
:
return
self
.
get
(
request
,
errors
=
_
(
'* Your password does not meet the requirements'
)
)
user
.
reset_password
(
password
)
return
HttpResponseRedirect
(
reverse
(
'users:reset-password-success'
))
...
...
apps/users/views/user.py
View file @
ee35ca36
...
...
@@ -33,9 +33,10 @@ from django.contrib.auth import logout as auth_logout
from
common.const
import
create_success_msg
,
update_success_msg
from
common.mixins
import
JSONResponseMixin
from
common.utils
import
get_logger
,
get_object_or_none
,
is_uuid
,
ssh_key_gen
from
common.models
import
Setting
from
..
import
forms
from
..models
import
User
,
UserGroup
from
..utils
import
AdminUserRequiredMixin
,
generate_otp_uri
,
check_otp_code
,
get_user_or_tmp_user
from
..utils
import
AdminUserRequiredMixin
,
generate_otp_uri
,
check_otp_code
,
get_user_or_tmp_user
,
get_password_check_rules
,
check_password_rules
from
..signals
import
post_user_create
from
..tasks
import
write_login_log_async
...
...
@@ -96,10 +97,27 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
success_message
=
update_success_msg
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'app'
:
_
(
'Users'
),
'action'
:
_
(
'Update user'
)}
check_rules
,
min_length
=
get_password_check_rules
()
context
=
{
'app'
:
_
(
'Users'
),
'action'
:
_
(
'Update user'
),
'password_check_rules'
:
check_rules
,
'min_length'
:
min_length
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
form_valid
(
self
,
form
):
password
=
form
.
cleaned_data
.
get
(
'password'
)
is_ok
=
check_password_rules
(
password
)
if
not
is_ok
:
form
.
add_error
(
"password"
,
_
(
"* Your password does not meet the requirements"
)
)
return
self
.
form_invalid
(
form
)
return
super
()
.
form_valid
(
form
)
class
UserBulkUpdateView
(
AdminUserRequiredMixin
,
TemplateView
):
model
=
User
...
...
@@ -318,8 +336,11 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
template_name
=
'users/user_profile.html'
def
get_context_data
(
self
,
**
kwargs
):
mfa_setting
=
Setting
.
objects
.
filter
(
name
=
'SECURITY_MFA_AUTH'
)
.
first
()
context
=
{
'action'
:
_
(
'Profile'
),
'mfa_setting'
:
mfa_setting
.
cleaned_value
if
mfa_setting
else
False
,
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
@@ -353,9 +374,12 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
return
self
.
request
.
user
def
get_context_data
(
self
,
**
kwargs
):
check_rules
,
min_length
=
get_password_check_rules
()
context
=
{
'app'
:
_
(
'Users'
),
'action'
:
_
(
'Password update'
),
'password_check_rules'
:
check_rules
,
'min_length'
:
min_length
,
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
@@ -364,6 +388,17 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
auth_logout
(
self
.
request
)
return
super
()
.
get_success_url
()
def
form_valid
(
self
,
form
):
password
=
form
.
cleaned_data
.
get
(
'new_password'
)
is_ok
=
check_password_rules
(
password
)
if
not
is_ok
:
form
.
add_error
(
"new_password"
,
_
(
"* Your password does not meet the requirements"
)
)
return
self
.
form_invalid
(
form
)
return
super
()
.
form_valid
(
form
)
class
UserPublicKeyUpdateView
(
LoginRequiredMixin
,
UpdateView
):
template_name
=
'users/user_pubkey_update.html'
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment