Commit ee35ca36 authored by BaiJiangJie's avatar BaiJiangJie

[Feature] 增加功能,安全设置(全局MFA设置,密码强度校验)

parent 526943a0
...@@ -168,3 +168,48 @@ class TerminalSettingForm(BaseForm): ...@@ -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')
)
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
<li> <li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a> <a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
......
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
<li> <li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a> <a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
......
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
<li> <li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a> <a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
......
{% 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 %}
...@@ -27,6 +27,9 @@ ...@@ -27,6 +27,9 @@
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i <a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a> class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
...@@ -39,6 +42,7 @@ ...@@ -39,6 +42,7 @@
</div> </div>
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}
<h3>{% trans "Basic setting" %}</h3> <h3>{% trans "Basic setting" %}</h3>
{% for field in form %} {% for field in form %}
{% if not field.field|is_bool_field %} {% if not field.field|is_bool_field %}
...@@ -60,6 +64,7 @@ ...@@ -60,6 +64,7 @@
{% endfor %} {% endfor %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans "Command storage" %}</h3> <h3>{% trans "Command storage" %}</h3>
<table class="table table-hover " id="task-history-list-table"> <table class="table table-hover " id="task-history-list-table">
<thead> <thead>
......
...@@ -11,4 +11,5 @@ urlpatterns = [ ...@@ -11,4 +11,5 @@ urlpatterns = [
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'), url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'),
] ]
...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _ ...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm TerminalSettingForm, SecuritySettingForm
from .mixins import AdminUserRequiredMixin from .mixins import AdminUserRequiredMixin
from .signals import ldap_auth_enable from .signals import ldap_auth_enable
...@@ -122,3 +122,27 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -122,3 +122,27 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) 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)
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-05-25 18:11+0800\n" "POT-Creation-Date: 2018-06-05 17:03+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n"
...@@ -21,15 +21,15 @@ msgstr "" ...@@ -21,15 +21,15 @@ msgstr ""
msgid "New node {}" msgid "New node {}"
msgstr "新节点 {}" msgstr "新节点 {}"
#: assets/api/node.py:242 #: assets/api/node.py:241
msgid "更新节点资产硬件信息: {}" msgid "更新节点资产硬件信息: {}"
msgstr "" msgstr ""
#: assets/api/node.py:255 #: assets/api/node.py:254
msgid "测试节点下资产是否可连接: {}" msgid "测试节点下资产是否可连接: {}"
msgstr "" 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:183
#: assets/templates/assets/asset_detail.html:191 #: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/system_user_detail.html:175 perms/models.py:33 #: assets/templates/assets/system_user_detail.html:175 perms/models.py:33
...@@ -37,7 +37,7 @@ msgid "Nodes" ...@@ -37,7 +37,7 @@ msgid "Nodes"
msgstr "节点管理" msgstr "节点管理"
#: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109 #: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109
#: assets/forms/asset.py:113 assets/models/asset.py:70 #: assets/forms/asset.py:113 assets/models/asset.py:79
#: assets/models/cluster.py:19 assets/models/user.py:72 #: assets/models/cluster.py:19 assets/models/user.py:72
#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25 #: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25
msgid "Admin user" msgid "Admin user"
...@@ -46,14 +46,14 @@ msgstr "管理用户" ...@@ -46,14 +46,14 @@ msgstr "管理用户"
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125 #: 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:35
#: assets/templates/assets/asset_create.html:37 #: assets/templates/assets/asset_create.html:37
#: assets/templates/assets/asset_list.html:74 #: assets/templates/assets/asset_list.html:75
#: assets/templates/assets/asset_update.html:40 #: assets/templates/assets/asset_update.html:40
#: assets/templates/assets/asset_update.html:42 #: assets/templates/assets/asset_update.html:42
#: assets/templates/assets/user_asset_list.html:34 #: assets/templates/assets/user_asset_list.html:34
msgid "Label" msgid "Label"
msgstr "标签" 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 #: assets/models/domain.py:46
msgid "Domain" msgid "Domain"
msgstr "网域" msgstr "网域"
...@@ -90,7 +90,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, ...@@ -90,7 +90,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
msgid "Select assets" msgid "Select assets"
msgstr "选择资产" msgstr "选择资产"
#: assets/forms/asset.py:105 assets/models/asset.py:63 #: assets/forms/asset.py:105 assets/models/asset.py:66
#: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53 #: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/domain_gateway_list.html:58
...@@ -99,7 +99,7 @@ msgid "Port" ...@@ -99,7 +99,7 @@ msgid "Port"
msgstr "端口" msgstr "端口"
#: assets/forms/domain.py:14 assets/forms/label.py:13 #: 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_detail.html:60
#: assets/templates/assets/domain_list.html:15 #: assets/templates/assets/domain_list.html:15
#: assets/templates/assets/label_list.html:16 #: assets/templates/assets/label_list.html:16
...@@ -129,8 +129,8 @@ msgstr "资产" ...@@ -129,8 +129,8 @@ msgstr "资产"
#: assets/templates/assets/label_list.html:14 #: assets/templates/assets/label_list.html:14
#: assets/templates/assets/system_user_detail.html:58 #: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:26 common/models.py:26 #: 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:72
#: common/templates/common/terminal_setting.html:85 ops/models/adhoc.py:36 #: 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 #: 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/models.py:29 perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:53 #: perms/templates/perms/asset_permission_list.html:53
...@@ -171,10 +171,10 @@ msgstr "密码或密钥密码" ...@@ -171,10 +171,10 @@ msgstr "密码或密钥密码"
#: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113 #: 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/forms.py:15 users/forms.py:23 users/forms.py:32 users/forms.py:44
#: users/templates/users/login.html:59 #: users/templates/users/login.html:59
#: users/templates/users/reset_password.html:52 #: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10 #: users/templates/users/user_create.html:10
#: users/templates/users/user_password_authentication.html:14 #: users/templates/users/user_password_authentication.html:14
#: users/templates/users/user_password_update.html:40 #: users/templates/users/user_password_update.html:42
#: users/templates/users/user_profile_update.html:40 #: users/templates/users/user_profile_update.html:40
#: users/templates/users/user_pubkey_update.html:40 #: users/templates/users/user_pubkey_update.html:40
msgid "Password" msgid "Password"
...@@ -202,11 +202,11 @@ msgid "" ...@@ -202,11 +202,11 @@ msgid ""
"than 2 system user" "than 2 system user"
msgstr "高优先级的系统用户将会作为默认登录用户" msgstr "高优先级的系统用户将会作为默认登录用户"
#: assets/models/asset.py:61 assets/models/domain.py:43 #: assets/models/asset.py:62 assets/models/domain.py:43
#: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61 #: assets/templates/assets/asset_detail.html:61
#: assets/templates/assets/asset_list.html:86 #: assets/templates/assets/asset_list.html:87
#: assets/templates/assets/domain_gateway_list.html:57 #: assets/templates/assets/domain_gateway_list.html:57
#: assets/templates/assets/system_user_asset.html:50 #: assets/templates/assets/system_user_asset.html:50
#: assets/templates/assets/user_asset_list.html:46 common/forms.py:144 #: assets/templates/assets/user_asset_list.html:46 common/forms.py:144
...@@ -217,10 +217,10 @@ msgstr "高优先级的系统用户将会作为默认登录用户" ...@@ -217,10 +217,10 @@ msgstr "高优先级的系统用户将会作为默认登录用户"
msgid "IP" msgid "IP"
msgstr "IP" msgstr "IP"
#: assets/models/asset.py:62 assets/templates/assets/_asset_list_modal.html:45 #: assets/models/asset.py:65 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/admin_user_assets.html:51 #: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/asset_detail.html:57 #: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_list.html:85 #: assets/templates/assets/asset_list.html:86
#: assets/templates/assets/system_user_asset.html:49 #: assets/templates/assets/system_user_asset.html:49
#: assets/templates/assets/user_asset_list.html:45 common/forms.py:143 #: assets/templates/assets/user_asset_list.html:45 common/forms.py:143
#: perms/templates/perms/asset_permission_asset.html:54 #: perms/templates/perms/asset_permission_asset.html:54
...@@ -229,82 +229,82 @@ msgstr "IP" ...@@ -229,82 +229,82 @@ msgstr "IP"
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: assets/models/asset.py:64 assets/templates/assets/asset_detail.html:97 #: assets/models/asset.py:68 assets/templates/assets/asset_detail.html:97
msgid "Platform" msgid "Platform"
msgstr "系统平台" 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 #: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105
msgid "Is active" msgid "Is active"
msgstr "激活" 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" msgid "Public IP"
msgstr "公网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" msgid "Asset number"
msgstr "资产编号" 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" msgid "Vendor"
msgstr "制造商" 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" msgid "Model"
msgstr "型号" 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" msgid "Serial number"
msgstr "序列号" msgstr "序列号"
#: assets/models/asset.py:81 #: assets/models/asset.py:97
msgid "CPU model" msgid "CPU model"
msgstr "CPU型号" msgstr "CPU型号"
#: assets/models/asset.py:82 #: assets/models/asset.py:98
msgid "CPU count" msgid "CPU count"
msgstr "CPU数量" msgstr "CPU数量"
#: assets/models/asset.py:83 #: assets/models/asset.py:99
msgid "CPU cores" msgid "CPU cores"
msgstr "CPU核数" 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" msgid "Memory"
msgstr "内存" msgstr "内存"
#: assets/models/asset.py:85 #: assets/models/asset.py:103
msgid "Disk total" msgid "Disk total"
msgstr "硬盘大小" msgstr "硬盘大小"
#: assets/models/asset.py:86 #: assets/models/asset.py:105
msgid "Disk info" msgid "Disk info"
msgstr "硬盘信息" msgstr "硬盘信息"
#: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:101 #: assets/models/asset.py:108 assets/templates/assets/asset_detail.html:101
msgid "OS" msgid "OS"
msgstr "操作系统" msgstr "操作系统"
#: assets/models/asset.py:89 #: assets/models/asset.py:110
msgid "OS version" msgid "OS version"
msgstr "系统版本" msgstr "系统版本"
#: assets/models/asset.py:90 #: assets/models/asset.py:112
msgid "OS arch" msgid "OS arch"
msgstr "系统架构" msgstr "系统架构"
#: assets/models/asset.py:91 #: assets/models/asset.py:114
msgid "Hostname raw" msgid "Hostname raw"
msgstr "主机名原始" 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_detail.html:220
#: assets/templates/assets/asset_update.html:38 templates/_nav.html:27 #: assets/templates/assets/asset_update.html:38 templates/_nav.html:27
msgid "Labels" msgid "Labels"
msgstr "标签管理" 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/models/cluster.py:28 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:117 #: assets/templates/assets/asset_detail.html:117
...@@ -316,7 +316,7 @@ msgstr "标签管理" ...@@ -316,7 +316,7 @@ msgstr "标签管理"
msgid "Created by" msgid "Created by"
msgstr "创建者" 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/domain.py:20 assets/models/group.py:22
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/domain_detail.html:68
...@@ -329,7 +329,7 @@ msgstr "创建者" ...@@ -329,7 +329,7 @@ msgstr "创建者"
msgid "Date created" msgid "Date created"
msgstr "创建日期" 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/cluster.py:29 assets/models/domain.py:18
#: assets/models/domain.py:47 assets/models/group.py:23 #: assets/models/domain.py:47 assets/models/group.py:23
#: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72 #: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72
...@@ -346,7 +346,7 @@ msgstr "创建日期" ...@@ -346,7 +346,7 @@ msgstr "创建日期"
#: users/models/user.py:75 users/templates/users/user_detail.html:119 #: 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_detail.html:67
#: users/templates/users/user_group_list.html:14 #: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:123 #: users/templates/users/user_profile.html:126
msgid "Comment" msgid "Comment"
msgstr "备注" msgstr "备注"
...@@ -434,11 +434,11 @@ msgstr "默认资产组" ...@@ -434,11 +434,11 @@ msgstr "默认资产组"
#: terminal/templates/terminal/session_list.html:71 users/forms.py:281 #: terminal/templates/terminal/session_list.html:71 users/forms.py:281
#: users/models/user.py:30 users/models/user.py:318 #: users/models/user.py:30 users/models/user.py:318
#: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:339 #: users/templates/users/user_group_list.html:13 users/views/user.py:360
msgid "User" msgid "User"
msgstr "用户" msgstr "用户"
#: assets/models/label.py:18 assets/models/node.py:18 #: assets/models/label.py:18 assets/models/node.py:16
#: assets/templates/assets/label_list.html:15 common/models.py:27 #: assets/templates/assets/label_list.html:15 common/models.py:27
msgid "Value" msgid "Value"
msgstr "值" msgstr "值"
...@@ -447,7 +447,7 @@ msgstr "值" ...@@ -447,7 +447,7 @@ msgstr "值"
msgid "Category" msgid "Category"
msgstr "分类" msgstr "分类"
#: assets/models/node.py:14 #: assets/models/node.py:15
msgid "Key" msgid "Key"
msgstr "" msgstr ""
...@@ -630,16 +630,17 @@ msgstr "其它" ...@@ -630,16 +630,17 @@ msgstr "其它"
#: assets/templates/assets/domain_create_update.html:16 #: assets/templates/assets/domain_create_update.html:16
#: assets/templates/assets/gateway_create_update.html:58 #: assets/templates/assets/gateway_create_update.html:58
#: assets/templates/assets/label_create_update.html:18 #: assets/templates/assets/label_create_update.html:18
#: common/templates/common/basic_setting.html:58 #: common/templates/common/basic_setting.html:61
#: common/templates/common/email_setting.html:59 #: common/templates/common/email_setting.html:62
#: common/templates/common/ldap_setting.html:59 #: common/templates/common/ldap_setting.html:62
#: common/templates/common/terminal_setting.html:101 #: common/templates/common/security_setting.html:70
#: common/templates/common/terminal_setting.html:106
#: perms/templates/perms/asset_permission_create_update.html:69 #: perms/templates/perms/asset_permission_create_update.html:69
#: terminal/templates/terminal/terminal_update.html:47 #: terminal/templates/terminal/terminal_update.html:47
#: users/templates/users/_user.html:46 #: users/templates/users/_user.html:46
#: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_password_update.html:58 #: users/templates/users/user_password_update.html:70
#: users/templates/users/user_profile.html:181 #: users/templates/users/user_profile.html:184
#: users/templates/users/user_profile_update.html:63 #: users/templates/users/user_profile_update.html:63
#: users/templates/users/user_pubkey_update.html:70 #: users/templates/users/user_pubkey_update.html:70
#: users/templates/users/user_pubkey_update.html:76 #: users/templates/users/user_pubkey_update.html:76
...@@ -650,15 +651,16 @@ msgstr "重置" ...@@ -650,15 +651,16 @@ msgstr "重置"
#: assets/templates/assets/admin_user_create_update.html:46 #: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24 #: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:67 #: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_list.html:107 #: assets/templates/assets/asset_list.html:108
#: assets/templates/assets/asset_update.html:71 #: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/domain_create_update.html:17 #: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:59 #: assets/templates/assets/gateway_create_update.html:59
#: assets/templates/assets/label_create_update.html:19 #: assets/templates/assets/label_create_update.html:19
#: common/templates/common/basic_setting.html:59 #: common/templates/common/basic_setting.html:62
#: common/templates/common/email_setting.html:60 #: common/templates/common/email_setting.html:63
#: common/templates/common/ldap_setting.html:60 #: common/templates/common/ldap_setting.html:63
#: common/templates/common/terminal_setting.html:103 #: common/templates/common/security_setting.html:71
#: common/templates/common/terminal_setting.html:108
#: perms/templates/perms/asset_permission_create_update.html:70 #: perms/templates/perms/asset_permission_create_update.html:70
#: terminal/templates/terminal/session_list.html:124 #: terminal/templates/terminal/session_list.html:124
#: terminal/templates/terminal/terminal_update.html:48 #: terminal/templates/terminal/terminal_update.html:48
...@@ -666,7 +668,7 @@ msgstr "重置" ...@@ -666,7 +668,7 @@ msgstr "重置"
#: users/templates/users/forgot_password.html:44 #: users/templates/users/forgot_password.html:44
#: users/templates/users/user_bulk_update.html:24 #: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:44 #: 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_profile_update.html:64
#: users/templates/users/user_pubkey_update.html:77 #: users/templates/users/user_pubkey_update.html:77
msgid "Submit" msgid "Submit"
...@@ -727,7 +729,7 @@ msgstr "测试" ...@@ -727,7 +729,7 @@ msgstr "测试"
#: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:85 #: assets/templates/assets/admin_user_list.html:85
#: assets/templates/assets/asset_detail.html:24 #: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_list.html:174 #: assets/templates/assets/asset_list.html:175
#: assets/templates/assets/domain_detail.html:24 #: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103 #: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:85 #: assets/templates/assets/domain_gateway_list.html:85
...@@ -743,15 +745,15 @@ msgstr "测试" ...@@ -743,15 +745,15 @@ msgstr "测试"
#: users/templates/users/user_group_detail.html:28 #: users/templates/users/user_group_detail.html:28
#: users/templates/users/user_group_list.html:43 #: users/templates/users/user_group_list.html:43
#: users/templates/users/user_list.html:76 #: users/templates/users/user_list.html:76
#: users/templates/users/user_profile.html:144 #: users/templates/users/user_profile.html:147
#: users/templates/users/user_profile.html:173 #: users/templates/users/user_profile.html:176
msgid "Update" msgid "Update"
msgstr "更新" msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28 #: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:86 #: assets/templates/assets/admin_user_list.html:86
#: assets/templates/assets/asset_detail.html:28 #: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_list.html:175 #: assets/templates/assets/asset_list.html:176
#: assets/templates/assets/domain_detail.html:28 #: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104 #: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:86 #: assets/templates/assets/domain_gateway_list.html:86
...@@ -782,7 +784,7 @@ msgstr "选择节点" ...@@ -782,7 +784,7 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200 #: assets/templates/assets/asset_detail.html:200
#: assets/templates/assets/asset_list.html:636 #: assets/templates/assets/asset_list.html:639
#: assets/templates/assets/system_user_detail.html:192 #: assets/templates/assets/system_user_detail.html:192
#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22 #: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108 #: terminal/templates/terminal/session_detail.html:108
...@@ -792,7 +794,7 @@ msgstr "选择节点" ...@@ -792,7 +794,7 @@ msgstr "选择节点"
#: users/templates/users/user_group_create_update.html:32 #: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:86 #: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:199 #: users/templates/users/user_list.html:199
#: users/templates/users/user_profile.html:215 #: users/templates/users/user_profile.html:218
msgid "Confirm" msgid "Confirm"
msgstr "确认" msgstr "确认"
...@@ -814,7 +816,7 @@ msgid "Ratio" ...@@ -814,7 +816,7 @@ msgid "Ratio"
msgstr "比例" msgstr "比例"
#: assets/templates/assets/admin_user_list.html:30 #: assets/templates/assets/admin_user_list.html:30
#: assets/templates/assets/asset_list.html:90 #: assets/templates/assets/asset_list.html:91
#: assets/templates/assets/domain_gateway_list.html:62 #: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:18 #: assets/templates/assets/domain_list.html:18
#: assets/templates/assets/label_list.html:17 #: assets/templates/assets/label_list.html:17
...@@ -843,19 +845,19 @@ msgstr "硬盘" ...@@ -843,19 +845,19 @@ msgstr "硬盘"
#: assets/templates/assets/asset_detail.html:121 #: assets/templates/assets/asset_detail.html:121
#: users/templates/users/user_detail.html:111 #: users/templates/users/user_detail.html:111
#: users/templates/users/user_profile.html:97 #: users/templates/users/user_profile.html:100
msgid "Date joined" msgid "Date joined"
msgstr "创建日期" msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:137 #: assets/templates/assets/asset_detail.html:137
#: terminal/templates/terminal/session_detail.html:81 #: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:130 #: users/templates/users/user_detail.html:130
#: users/templates/users/user_profile.html:135 #: users/templates/users/user_profile.html:138
msgid "Quick modify" msgid "Quick modify"
msgstr "快速修改" msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:143 #: assets/templates/assets/asset_detail.html:143
#: assets/templates/assets/asset_list.html:88 #: assets/templates/assets/asset_list.html:89
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:35 #: assets/templates/assets/user_asset_list.html:47 perms/models.py:35
#: perms/models.py:79 #: perms/models.py:79
#: perms/templates/perms/asset_permission_create_update.html:47 #: perms/templates/perms/asset_permission_create_update.html:47
...@@ -885,97 +887,97 @@ msgstr "刷新" ...@@ -885,97 +887,97 @@ msgstr "刷新"
msgid "Update successfully!" msgid "Update successfully!"
msgstr "更新成功" msgstr "更新成功"
#: assets/templates/assets/asset_list.html:62 assets/views/asset.py:97 #: assets/templates/assets/asset_list.html:63 assets/views/asset.py:97
msgid "Create asset" msgid "Create asset"
msgstr "创建资产" msgstr "创建资产"
#: assets/templates/assets/asset_list.html:66 #: assets/templates/assets/asset_list.html:67
#: users/templates/users/user_list.html:7 #: users/templates/users/user_list.html:7
msgid "Import" msgid "Import"
msgstr "导入" msgstr "导入"
#: assets/templates/assets/asset_list.html:69 #: assets/templates/assets/asset_list.html:70
#: users/templates/users/user_list.html:10 #: users/templates/users/user_list.html:10
msgid "Export" msgid "Export"
msgstr "导出" msgstr "导出"
#: assets/templates/assets/asset_list.html:87 #: assets/templates/assets/asset_list.html:88
msgid "Hardware" msgid "Hardware"
msgstr "硬件" msgstr "硬件"
#: assets/templates/assets/asset_list.html:99 #: assets/templates/assets/asset_list.html:100
#: users/templates/users/user_list.html:37 #: users/templates/users/user_list.html:37
msgid "Delete selected" msgid "Delete selected"
msgstr "批量删除" msgstr "批量删除"
#: assets/templates/assets/asset_list.html:100 #: assets/templates/assets/asset_list.html:101
#: users/templates/users/user_list.html:38 #: users/templates/users/user_list.html:38
msgid "Update selected" msgid "Update selected"
msgstr "批量更新" msgstr "批量更新"
#: assets/templates/assets/asset_list.html:101 #: assets/templates/assets/asset_list.html:102
msgid "Remove from this node" msgid "Remove from this node"
msgstr "从节点移除" msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:102 #: assets/templates/assets/asset_list.html:103
#: users/templates/users/user_list.html:39 #: users/templates/users/user_list.html:39
msgid "Deactive selected" msgid "Deactive selected"
msgstr "禁用所选" msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:103 #: assets/templates/assets/asset_list.html:104
#: users/templates/users/user_list.html:40 #: users/templates/users/user_list.html:40
msgid "Active selected" msgid "Active selected"
msgstr "激活所选" msgstr "激活所选"
#: assets/templates/assets/asset_list.html:120 #: assets/templates/assets/asset_list.html:121
msgid "Add node" msgid "Add node"
msgstr "新建节点" msgstr "新建节点"
#: assets/templates/assets/asset_list.html:121 #: assets/templates/assets/asset_list.html:122
msgid "Rename node" msgid "Rename node"
msgstr "重命名节点" msgstr "重命名节点"
#: assets/templates/assets/asset_list.html:122 #: assets/templates/assets/asset_list.html:123
msgid "Delete node" msgid "Delete node"
msgstr "删除节点" msgstr "删除节点"
#: assets/templates/assets/asset_list.html:124 #: assets/templates/assets/asset_list.html:125
msgid "Add assets to node" msgid "Add assets to node"
msgstr "添加资产到节点" msgstr "添加资产到节点"
#: assets/templates/assets/asset_list.html:125 #: assets/templates/assets/asset_list.html:126
msgid "Move assets to node" msgid "Move assets to node"
msgstr "移动资产到节点" msgstr "移动资产到节点"
#: assets/templates/assets/asset_list.html:127 #: assets/templates/assets/asset_list.html:128
msgid "Refresh node hardware info" msgid "Refresh node hardware info"
msgstr "更新节点资产硬件信息" msgstr "更新节点资产硬件信息"
#: assets/templates/assets/asset_list.html:128 #: assets/templates/assets/asset_list.html:129
msgid "Test node connective" msgid "Test node connective"
msgstr "测试节点资产可连接性" msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:130 #: assets/templates/assets/asset_list.html:131
msgid "Display only current node assets" msgid "Display only current node assets"
msgstr "仅显示当前节点资产" msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:131 #: assets/templates/assets/asset_list.html:132
msgid "Displays all child node assets" msgid "Displays all child node assets"
msgstr "显示所有子节点资产" msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:217 #: assets/templates/assets/asset_list.html:218
msgid "Create node failed" msgid "Create node failed"
msgstr "创建节点失败" msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:229 #: assets/templates/assets/asset_list.html:230
msgid "Have child node, cancel" msgid "Have child node, cancel"
msgstr "存在子节点,不能删除" msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:231 #: assets/templates/assets/asset_list.html:232
msgid "Have assets, cancel" msgid "Have assets, cancel"
msgstr "存在资产,不能删除" msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:631 #: assets/templates/assets/asset_list.html:634
#: assets/templates/assets/system_user_list.html:133 #: assets/templates/assets/system_user_list.html:133
#: users/templates/users/user_detail.html:357 #: users/templates/users/user_detail.html:357
#: users/templates/users/user_detail.html:382 #: users/templates/users/user_detail.html:382
...@@ -984,20 +986,20 @@ msgstr "存在资产,不能删除" ...@@ -984,20 +986,20 @@ msgstr "存在资产,不能删除"
msgid "Are you sure?" msgid "Are you sure?"
msgstr "你确认吗?" msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:632 #: assets/templates/assets/asset_list.html:635
msgid "This will delete the selected assets !!!" msgid "This will delete the selected assets !!!"
msgstr "删除选择资产" msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:640 #: assets/templates/assets/asset_list.html:643
msgid "Asset Deleted." msgid "Asset Deleted."
msgstr "已被删除" msgstr "已被删除"
#: assets/templates/assets/asset_list.html:641 #: assets/templates/assets/asset_list.html:644
#: assets/templates/assets/asset_list.html:646 #: assets/templates/assets/asset_list.html:649
msgid "Asset Delete" msgid "Asset Delete"
msgstr "删除" msgstr "删除"
#: assets/templates/assets/asset_list.html:645 #: assets/templates/assets/asset_list.html:648
msgid "Asset Deleting failed." msgid "Asset Deleting failed."
msgstr "删除失败" msgstr "删除失败"
...@@ -1033,8 +1035,8 @@ msgstr "创建网关" ...@@ -1033,8 +1035,8 @@ msgstr "创建网关"
#: assets/templates/assets/domain_gateway_list.html:87 #: assets/templates/assets/domain_gateway_list.html:87
#: assets/templates/assets/domain_gateway_list.html:89 #: assets/templates/assets/domain_gateway_list.html:89
#: common/templates/common/email_setting.html:58 #: common/templates/common/email_setting.html:61
#: common/templates/common/ldap_setting.html:58 #: common/templates/common/ldap_setting.html:61
msgid "Test connection" msgid "Test connection"
msgstr "测试连接" msgstr "测试连接"
...@@ -1376,7 +1378,7 @@ msgstr "密码认证" ...@@ -1376,7 +1378,7 @@ msgstr "密码认证"
msgid "Public key auth" msgid "Public key auth"
msgstr "密钥认证" msgstr "密钥认证"
#: common/forms.py:159 common/templates/common/terminal_setting.html:63 #: common/forms.py:159 common/templates/common/terminal_setting.html:68
#: terminal/forms.py:30 terminal/models.py:20 #: terminal/forms.py:30 terminal/models.py:20
msgid "Command storage" msgid "Command storage"
msgstr "命令存储" msgstr "命令存储"
...@@ -1387,7 +1389,7 @@ msgid "" ...@@ -1387,7 +1389,7 @@ msgid ""
"other storage and some terminal using" "other storage and some terminal using"
msgstr "设置终端命令存储,default是默认用的存储方式" msgstr "设置终端命令存储,default是默认用的存储方式"
#: common/forms.py:165 common/templates/common/terminal_setting.html:81 #: common/forms.py:165 common/templates/common/terminal_setting.html:86
#: terminal/forms.py:35 terminal/models.py:21 #: terminal/forms.py:35 terminal/models.py:21
msgid "Replay storage" msgid "Replay storage"
msgstr "录像存储" msgstr "录像存储"
...@@ -1398,6 +1400,60 @@ msgid "" ...@@ -1398,6 +1400,60 @@ msgid ""
"other storage and some terminal using" "other storage and some terminal using"
msgstr "设置终端录像存储,default是默认用的存储方式" 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 #: common/mixins.py:29
msgid "is discard" msgid "is discard"
msgstr "" msgstr ""
...@@ -1413,14 +1469,16 @@ msgstr "启用" ...@@ -1413,14 +1469,16 @@ msgstr "启用"
#: common/templates/common/basic_setting.html:15 #: common/templates/common/basic_setting.html:15
#: common/templates/common/email_setting.html:15 #: common/templates/common/email_setting.html:15
#: common/templates/common/ldap_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:16
#: common/templates/common/terminal_setting.html:42 common/views.py:22 #: common/templates/common/terminal_setting.html:46 common/views.py:22
msgid "Basic setting" msgid "Basic setting"
msgstr "基本设置" msgstr "基本设置"
#: common/templates/common/basic_setting.html:18 #: common/templates/common/basic_setting.html:18
#: common/templates/common/email_setting.html:18 #: common/templates/common/email_setting.html:18
#: common/templates/common/ldap_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 #: common/templates/common/terminal_setting.html:20 common/views.py:48
msgid "Email setting" msgid "Email setting"
msgstr "邮件设置" msgstr "邮件设置"
...@@ -1428,6 +1486,7 @@ msgstr "邮件设置" ...@@ -1428,6 +1486,7 @@ msgstr "邮件设置"
#: common/templates/common/basic_setting.html:21 #: common/templates/common/basic_setting.html:21
#: common/templates/common/email_setting.html:21 #: common/templates/common/email_setting.html:21
#: common/templates/common/ldap_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 #: common/templates/common/terminal_setting.html:24 common/views.py:74
msgid "LDAP setting" msgid "LDAP setting"
msgstr "LDAP设置" msgstr "LDAP设置"
...@@ -1435,12 +1494,29 @@ msgstr "LDAP设置" ...@@ -1435,12 +1494,29 @@ msgstr "LDAP设置"
#: common/templates/common/basic_setting.html:24 #: common/templates/common/basic_setting.html:24
#: common/templates/common/email_setting.html:24 #: common/templates/common/email_setting.html:24
#: common/templates/common/ldap_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 #: common/templates/common/terminal_setting.html:28 common/views.py:104
msgid "Terminal setting" msgid "Terminal setting"
msgstr "终端设置" msgstr "终端设置"
#: common/templates/common/terminal_setting.html:68 #: common/templates/common/basic_setting.html:27
#: common/templates/common/terminal_setting.html:86 #: 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 #: users/templates/users/login_log_list.html:50
msgid "Type" msgid "Type"
msgstr "类型" msgstr "类型"
...@@ -1450,11 +1526,12 @@ msgid "Special char not allowed" ...@@ -1450,11 +1526,12 @@ msgid "Special char not allowed"
msgstr "不能包含特殊字符" msgstr "不能包含特殊字符"
#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:103 #: 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" msgid "Settings"
msgstr "系统设置" msgstr "系统设置"
#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:116 #: 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" msgid "Update setting successfully, please restart program"
msgstr "更新设置成功, 请手动重启程序" msgstr "更新设置成功, 请手动重启程序"
...@@ -1754,7 +1831,7 @@ msgstr "" ...@@ -1754,7 +1831,7 @@ msgstr ""
#: perms/models.py:37 perms/models.py:80 #: perms/models.py:37 perms/models.py:80
#: perms/templates/perms/asset_permission_detail.html:90 #: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:80 users/templates/users/user_detail.html:103 #: users/models/user.py:80 users/templates/users/user_detail.html:103
#: users/templates/users/user_profile.html:105 #: users/templates/users/user_profile.html:108
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
...@@ -1884,11 +1961,11 @@ msgstr "文档" ...@@ -1884,11 +1961,11 @@ msgstr "文档"
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:121 #: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:121
#: users/templates/users/_user.html:39 #: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39 #: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:37 #: users/templates/users/user_password_update.html:39
#: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57 #: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:322 #: users/templates/users/user_pubkey_update.html:37 users/views/user.py:342
msgid "Profile" msgid "Profile"
msgstr "个人信息" msgstr "个人信息"
...@@ -1945,13 +2022,13 @@ msgstr "关闭" ...@@ -1945,13 +2022,13 @@ msgstr "关闭"
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95 #: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
#: users/views/login.py:263 users/views/login.py:321 users/views/user.py:64 #: users/views/login.py:284 users/views/login.py:342 users/views/user.py:65
#: users/views/user.py:79 users/views/user.py:99 users/views/user.py:155 #: users/views/user.py:80 users/views/user.py:102 users/views/user.py:173
#: users/views/user.py:310 users/views/user.py:357 users/views/user.py:379 #: users/views/user.py:328 users/views/user.py:379 users/views/user.py:414
msgid "Users" msgid "Users"
msgstr "用户管理" msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:65 #: templates/_nav.html:13 users/views/user.py:66
msgid "User list" msgid "User list"
msgstr "用户列表" msgstr "用户列表"
...@@ -2356,7 +2433,7 @@ msgstr "复制你的公钥到这里" ...@@ -2356,7 +2433,7 @@ msgstr "复制你的公钥到这里"
#: users/forms.py:231 users/models/user.py:72 #: users/forms.py:231 users/models/user.py:72
#: users/templates/users/first_login.html:42 #: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:43 #: users/templates/users/user_password_update.html:45
#: users/templates/users/user_profile.html:68 #: users/templates/users/user_profile.html:68
#: users/templates/users/user_profile_update.html:43 #: users/templates/users/user_profile_update.html:43
#: users/templates/users/user_pubkey_update.html:43 #: users/templates/users/user_pubkey_update.html:43
...@@ -2396,13 +2473,13 @@ msgid "Application" ...@@ -2396,13 +2473,13 @@ msgid "Application"
msgstr "应用程序" msgstr "应用程序"
#: users/models/user.py:34 users/templates/users/user_profile.html:92 #: 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:159
#: users/templates/users/user_profile.html:162
msgid "Disable" msgid "Disable"
msgstr "禁用" msgstr "禁用"
#: users/models/user.py:35 users/templates/users/user_profile.html:90 #: users/models/user.py:35 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166
msgid "Enable" msgid "Enable"
msgstr "启用" msgstr "启用"
...@@ -2539,22 +2616,34 @@ msgstr "6位数字" ...@@ -2539,22 +2616,34 @@ msgstr "6位数字"
msgid "Can't provide security? Please contact the administrator!" msgid "Can't provide security? Please contact the administrator!"
msgstr "如果不能提供MFA验证码,请联系管理员!" msgstr "如果不能提供MFA验证码,请联系管理员!"
#: users/templates/users/reset_password.html:45 #: users/templates/users/reset_password.html:46
#: users/templates/users/user_detail.html:348 users/utils.py:76 #: users/templates/users/user_detail.html:348 users/utils.py:80
msgid "Reset password" msgid "Reset password"
msgstr "重置密码" 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" msgid "Password again"
msgstr "再次输入密码" msgstr "再次输入密码"
#: users/templates/users/reset_password.html:57 #: users/templates/users/reset_password.html:68
#: users/templates/users/user_profile.html:20 #: users/templates/users/user_profile.html:20
msgid "Setting" msgid "Setting"
msgstr "设置" msgstr "设置"
#: users/templates/users/user_create.html:4 #: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:16 users/views/user.py:79 #: users/templates/users/user_list.html:16 users/views/user.py:80
msgid "Create user" msgid "Create user"
msgstr "创建用户" msgstr "创建用户"
...@@ -2563,7 +2652,7 @@ msgid "Reset link will be generated and sent to the user. " ...@@ -2563,7 +2652,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户" msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19 #: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:156 #: users/templates/users/user_granted_asset.html:18 users/views/user.py:174
msgid "User detail" msgid "User detail"
msgstr "用户详情" msgstr "用户详情"
...@@ -2583,7 +2672,7 @@ msgid "Disabled" ...@@ -2583,7 +2672,7 @@ msgid "Disabled"
msgstr "禁用" msgstr "禁用"
#: users/templates/users/user_detail.html:115 #: users/templates/users/user_detail.html:115
#: users/templates/users/user_profile.html:101 #: users/templates/users/user_profile.html:104
msgid "Last login" msgid "Last login"
msgstr "最后登录" msgstr "最后登录"
...@@ -2631,14 +2720,14 @@ msgid "This will reset the user public key and send a reset mail" ...@@ -2631,14 +2720,14 @@ msgid "This will reset the user public key and send a reset mail"
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱" msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
#: users/templates/users/user_detail.html:400 #: users/templates/users/user_detail.html:400
#: users/templates/users/user_profile.html:204 #: users/templates/users/user_profile.html:207
msgid "Successfully updated the SSH public key." msgid "Successfully updated the SSH public key."
msgstr "更新ssh密钥成功" msgstr "更新ssh密钥成功"
#: users/templates/users/user_detail.html:401 #: users/templates/users/user_detail.html:401
#: users/templates/users/user_detail.html:405 #: users/templates/users/user_detail.html:405
#: users/templates/users/user_profile.html:205 #: users/templates/users/user_profile.html:208
#: users/templates/users/user_profile.html:210 #: users/templates/users/user_profile.html:213
msgid "User SSH public key update" msgid "User SSH public key update"
msgstr "ssh密钥" msgstr "ssh密钥"
...@@ -2694,28 +2783,32 @@ msgstr "删除" ...@@ -2694,28 +2783,32 @@ msgstr "删除"
msgid "User Deleting failed." msgid "User Deleting failed."
msgstr "用户删除失败" msgstr "用户删除失败"
#: users/templates/users/user_profile.html:109 users/views/user.py:185 #: users/templates/users/user_profile.html:95
#: users/views/user.py:239 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" msgid "User groups"
msgstr "用户组" msgstr "用户组"
#: users/templates/users/user_profile.html:141 #: users/templates/users/user_profile.html:144
msgid "Update password" msgid "Update password"
msgstr "更改密码" msgstr "更改密码"
#: users/templates/users/user_profile.html:149 #: users/templates/users/user_profile.html:152
msgid "Update MFA settings" msgid "Update MFA settings"
msgstr "更改MFA设置" msgstr "更改MFA设置"
#: users/templates/users/user_profile.html:170 #: users/templates/users/user_profile.html:173
msgid "Update SSH public key" msgid "Update SSH public key"
msgstr "更改SSH密钥" msgstr "更改SSH密钥"
#: users/templates/users/user_profile.html:178 #: users/templates/users/user_profile.html:181
msgid "Reset public key and download" msgid "Reset public key and download"
msgstr "重置并下载SSH密钥" msgstr "重置并下载SSH密钥"
#: users/templates/users/user_profile.html:208 #: users/templates/users/user_profile.html:211
msgid "Failed to update SSH public key." msgid "Failed to update SSH public key."
msgstr "更新密钥失败" msgstr "更新密钥失败"
...@@ -2735,15 +2828,15 @@ msgstr "更新密钥" ...@@ -2735,15 +2828,15 @@ msgstr "更新密钥"
msgid "Or reset by server" msgid "Or reset by server"
msgstr "或者重置并下载密钥" msgstr "或者重置并下载密钥"
#: users/templates/users/user_update.html:4 users/views/user.py:99 #: users/templates/users/user_update.html:4 users/views/user.py:103
msgid "Update user" msgid "Update user"
msgstr "更新用户" msgstr "更新用户"
#: users/utils.py:37 #: users/utils.py:41
msgid "Create account successfully" msgid "Create account successfully"
msgstr "创建账户成功" msgstr "创建账户成功"
#: users/utils.py:39 #: users/utils.py:43
#, fuzzy, python-format #, fuzzy, python-format
msgid "" msgid ""
"\n" "\n"
...@@ -2788,7 +2881,7 @@ msgstr "" ...@@ -2788,7 +2881,7 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:78 #: users/utils.py:82
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
...@@ -2832,11 +2925,11 @@ msgstr "" ...@@ -2832,11 +2925,11 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:109 #: users/utils.py:113
msgid "SSH Key Reset" msgid "SSH Key Reset"
msgstr "重置ssh密钥" msgstr "重置ssh密钥"
#: users/utils.py:111 #: users/utils.py:115
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
...@@ -2861,18 +2954,22 @@ msgstr "" ...@@ -2861,18 +2954,22 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:144 #: users/utils.py:148
msgid "User not exist" msgid "User not exist"
msgstr "用户不存在" msgstr "用户不存在"
#: users/utils.py:146 #: users/utils.py:150
msgid "Disabled or expired" msgid "Disabled or expired"
msgstr "禁用或失效" msgstr "禁用或失效"
#: users/utils.py:159 #: users/utils.py:163
msgid "Password or SSH public key invalid" msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法" msgstr "密码或密钥不合法"
#: users/utils.py:290 users/utils.py:300
msgid "Bit"
msgstr " 位"
#: users/views/group.py:29 #: users/views/group.py:29
msgid "User group list" msgid "User group list"
msgstr "用户组列表" msgstr "用户组列表"
...@@ -2885,99 +2982,103 @@ msgstr "更新用户组" ...@@ -2885,99 +2982,103 @@ msgstr "更新用户组"
msgid "User group granted asset" msgid "User group granted asset"
msgstr "用户组授权资产" msgstr "用户组授权资产"
#: users/views/login.py:59 #: users/views/login.py:62
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:125 users/views/user.py:464 users/views/user.py:489 #: users/views/login.py:135 users/views/user.py:499 users/views/user.py:524
msgid "MFA code invalid" msgid "MFA code invalid"
msgstr "MFA码认证失败" msgstr "MFA码认证失败"
#: users/views/login.py:151 #: users/views/login.py:161
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
#: users/views/login.py:152 #: users/views/login.py:162
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:168 #: users/views/login.py:178
msgid "Email address invalid, please input again" msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入" msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:181 #: users/views/login.py:191
msgid "Send reset password message" msgid "Send reset password message"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/views/login.py:182 #: users/views/login.py:192
msgid "Send reset password mail success, login your mail box and follow it " msgid "Send reset password mail success, login your mail box and follow it "
msgstr "" msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:195 #: users/views/login.py:205
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
#: users/views/login.py:196 #: users/views/login.py:206
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面" msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:213 users/views/login.py:226 #: users/views/login.py:227 users/views/login.py:240
msgid "Token invalid or expired" msgid "Token invalid or expired"
msgstr "Token错误或失效" msgstr "Token错误或失效"
#: users/views/login.py:222 #: users/views/login.py:236
msgid "Password not same" msgid "Password not same"
msgstr "密码不一致" 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" msgid "First login"
msgstr "首次登陆" msgstr "首次登陆"
#: users/views/login.py:322 #: users/views/login.py:343
msgid "Login log list" msgid "Login log list"
msgstr "登录日志" msgstr "登录日志"
#: users/views/user.py:109 #: users/views/user.py:127
msgid "Bulk update user success" msgid "Bulk update user success"
msgstr "批量更新用户成功" msgstr "批量更新用户成功"
#: users/views/user.py:214 #: users/views/user.py:232
msgid "Invalid file." msgid "Invalid file."
msgstr "文件不合法" msgstr "文件不合法"
#: users/views/user.py:311 #: users/views/user.py:329
msgid "User granted assets" msgid "User granted assets"
msgstr "用户授权资产" msgstr "用户授权资产"
#: users/views/user.py:340 #: users/views/user.py:361
msgid "Profile setting" msgid "Profile setting"
msgstr "个人信息设置" msgstr "个人信息设置"
#: users/views/user.py:358 #: users/views/user.py:380
msgid "Password update" msgid "Password update"
msgstr "密码更新" msgstr "密码更新"
#: users/views/user.py:380 #: users/views/user.py:415
msgid "Public key update" msgid "Public key update"
msgstr "密钥更新" msgstr "密钥更新"
#: users/views/user.py:421 #: users/views/user.py:456
msgid "Password invalid" msgid "Password invalid"
msgstr "用户名或密码无效" msgstr "用户名或密码无效"
#: users/views/user.py:515 #: users/views/user.py:550
msgid "MFA enable success" msgid "MFA enable success"
msgstr "MFA 绑定成功" msgstr "MFA 绑定成功"
#: users/views/user.py:516 #: users/views/user.py:551
msgid "MFA enable success, return login page" msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面" msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:518 #: users/views/user.py:553
msgid "MFA disable success" msgid "MFA disable success"
msgstr "MFA 解绑成功" msgstr "MFA 解绑成功"
#: users/views/user.py:519 #: users/views/user.py:554
msgid "MFA disable success, return login page" msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面" msgstr "MFA 解绑成功,返回登录页面"
...@@ -401,6 +401,9 @@ TERMINAL_REPLAY_STORAGE = { ...@@ -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 # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = { BOOTSTRAP3 = {
'horizontal_label_class': 'col-md-2', 'horizontal_label_class': 'col-md-2',
......
...@@ -609,3 +609,91 @@ function setUrlParam(url, name, value) { ...@@ -609,3 +609,91 @@ function setUrlParam(url, name, value) {
} }
return url 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);
}
/*!
* 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
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script> <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 %} {% block custom_head_css_js_create %} {% endblock %}
{% endblock %} {% endblock %}
......
...@@ -72,7 +72,7 @@ class UserCreateUpdateForm(forms.ModelForm): ...@@ -72,7 +72,7 @@ class UserCreateUpdateForm(forms.ModelForm):
'data-placeholder': _('Join user groups') 'data-placeholder': _('Join user groups')
} }
), ),
'otp_level': forms.RadioSelect() 'otp_level': forms.RadioSelect(),
} }
def clean_public_key(self): def clean_public_key(self):
......
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
</div> </div>
</div> </div>
</form> </form>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script> <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script src="{% static "js/jumpserver.js" %}"></script> <script src="{% static "js/jumpserver.js" %}"></script>
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
</head> </head>
...@@ -49,10 +50,20 @@ ...@@ -49,10 +50,20 @@
<p class="red-fonts">{{ errors }}</p> <p class="red-fonts">{{ errors }}</p>
{% endif %} {% endif %}
<div class="form-group"> <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>
<div class="form-group"> <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> </div>
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button> <button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
...@@ -79,4 +90,33 @@ ...@@ -79,4 +90,33 @@
</body> </body>
</html> </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>
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
<link href="{% static "css/plugins/cropper/cropper.min.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/cropper/cropper.min.css" %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script> <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> <style>
.crop { .crop {
...@@ -50,6 +52,16 @@ ...@@ -50,6 +52,16 @@
{% csrf_token %} {% csrf_token %}
{% bootstrap_field form.old_password layout="horizontal" %} {% bootstrap_field form.old_password layout="horizontal" %}
{% bootstrap_field form.new_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" %} {% bootstrap_field form.confirm_password layout="horizontal" %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
...@@ -71,5 +83,34 @@ ...@@ -71,5 +83,34 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script> <script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script>
<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> </script>
{% endblock %} {% endblock %}
...@@ -91,6 +91,9 @@ ...@@ -91,6 +91,9 @@
{% else %} {% else %}
{% trans 'Disable' %} {% trans 'Disable' %}
{% endif %} {% endif %}
{% if mfa_setting %}
( {% trans 'Administrator Settings force MFA login' %} )
{% endif %}
</td> </td>
</tr> </tr>
<tr> <tr>
...@@ -152,7 +155,7 @@ ...@@ -152,7 +155,7 @@
<a type="button" class="btn btn-primary btn-xs" style="width: 54px" id="" <a type="button" class="btn btn-primary btn-xs" style="width: 54px" id=""
href=" href="
{% if request.user.otp_enabled and request.user.otp_secret_key %} {% 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' %} " disabled >{% trans 'Disable' %}
{% else %} {% else %}
{% url 'users:user-otp-disable-authentication' %} {% url 'users:user-otp-disable-authentication' %}
......
...@@ -4,5 +4,50 @@ ...@@ -4,5 +4,50 @@
{% block user_template_title %}{% trans "Update user" %}{% endblock %} {% block user_template_title %}{% trans "Update user" %}{% endblock %}
{% block password %} {% block password %}
{% bootstrap_field form.password layout="horizontal" %} {% 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" %} {% bootstrap_field form.public_key layout="horizontal" %}
{% endblock %} {% 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 %}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# #
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import re
import pyotp import pyotp
import base64 import base64
import logging import logging
...@@ -18,8 +19,11 @@ from django.core.cache import cache ...@@ -18,8 +19,11 @@ from django.core.cache import cache
from common.tasks import send_mail_async from common.tasks import send_mail_async
from common.utils import reverse, get_object_or_none from common.utils import reverse, get_object_or_none
from common.models import Setting
from common.forms import SecuritySettingForm
from .models import User, LoginLog from .models import User, LoginLog
logger = logging.getLogger('jumpserver') logger = logging.getLogger('jumpserver')
...@@ -271,3 +275,60 @@ def generate_otp_uri(request, issuer="Jumpserver"): ...@@ -271,3 +275,60 @@ def generate_otp_uri(request, issuer="Jumpserver"):
def check_otp_code(otp_secret_key, otp_code): def check_otp_code(otp_secret_key, otp_code):
totp = pyotp.TOTP(otp_secret_key) totp = pyotp.TOTP(otp_secret_key)
return totp.verify(otp_code) 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)
...@@ -23,9 +23,10 @@ from django.conf import settings ...@@ -23,9 +23,10 @@ from django.conf import settings
from common.utils import get_object_or_none from common.utils import get_object_or_none
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from common.models import Setting
from ..models import User, LoginLog from ..models import User, LoginLog
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, redirect_user_first_login_or_index, \ 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 ..tasks import write_login_log_async
from .. import forms from .. import forms
...@@ -81,17 +82,24 @@ class UserLoginView(FormView): ...@@ -81,17 +82,24 @@ class UserLoginView(FormView):
def get_success_url(self): def get_success_url(self):
user = get_user_or_tmp_user(self.request) user = get_user_or_tmp_user(self.request)
if user.otp_enabled and user.otp_secret_key: mfa_setting = Setting.objects.filter(name='SECURITY_MFA_AUTH').first()
# 1,2 & T if mfa_setting and mfa_setting.cleaned_value:
return reverse('users:login-otp') if user.otp_enabled and user.otp_secret_key:
elif user.otp_enabled and not user.otp_secret_key: return reverse('users:login-otp')
# 1,2 & F else:
return reverse('users:user-otp-enable-authentication') return reverse('users:user-otp-enable-authentication')
elif not user.otp_enabled: else:
# 0 & T,F if user.otp_enabled and user.otp_secret_key:
auth_login(self.request, user) # 1,2 & T
self.write_login_log() return reverse('users:login-otp')
return redirect_user_first_login_or_index(self.request, self.redirect_field_name) elif user.otp_enabled and not user.otp_secret_key:
# 1,2 & F
return reverse('users:user-otp-enable-authentication')
elif not user.otp_enabled:
# 0 & T,F
auth_login(self.request, user)
self.write_login_log()
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -211,6 +219,10 @@ class UserResetPasswordView(TemplateView): ...@@ -211,6 +219,10 @@ class UserResetPasswordView(TemplateView):
token = request.GET.get('token') token = request.GET.get('token')
user = User.validate_reset_token(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: if not user:
kwargs.update({'errors': _('Token invalid or expired')}) kwargs.update({'errors': _('Token invalid or expired')})
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
...@@ -227,6 +239,13 @@ class UserResetPasswordView(TemplateView): ...@@ -227,6 +239,13 @@ class UserResetPasswordView(TemplateView):
if not user: if not user:
return self.get(request, errors=_('Token invalid or expired')) 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) user.reset_password(password)
return HttpResponseRedirect(reverse('users:reset-password-success')) return HttpResponseRedirect(reverse('users:reset-password-success'))
......
...@@ -33,9 +33,10 @@ from django.contrib.auth import logout as auth_logout ...@@ -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.const import create_success_msg, update_success_msg
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
from common.models import Setting
from .. import forms from .. import forms
from ..models import User, UserGroup 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 ..signals import post_user_create
from ..tasks import write_login_log_async from ..tasks import write_login_log_async
...@@ -96,10 +97,27 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -96,10 +97,27 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
success_message = update_success_msg success_message = update_success_msg
def get_context_data(self, **kwargs): 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) kwargs.update(context)
return super().get_context_data(**kwargs) 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): class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView):
model = User model = User
...@@ -318,8 +336,11 @@ class UserProfileView(LoginRequiredMixin, TemplateView): ...@@ -318,8 +336,11 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
template_name = 'users/user_profile.html' template_name = 'users/user_profile.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
mfa_setting = Setting.objects.filter(name='SECURITY_MFA_AUTH').first()
context = { context = {
'action': _('Profile'), 'action': _('Profile'),
'mfa_setting': mfa_setting.cleaned_value if mfa_setting else False,
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -353,9 +374,12 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView): ...@@ -353,9 +374,12 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
return self.request.user return self.request.user
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
check_rules, min_length = get_password_check_rules()
context = { context = {
'app': _('Users'), 'app': _('Users'),
'action': _('Password update'), 'action': _('Password update'),
'password_check_rules': check_rules,
'min_length': min_length,
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -364,6 +388,17 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView): ...@@ -364,6 +388,17 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
auth_logout(self.request) auth_logout(self.request)
return super().get_success_url() 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): class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView):
template_name = 'users/user_pubkey_update.html' template_name = 'users/user_pubkey_update.html'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment