Commit ee35ca36 authored by BaiJiangJie's avatar BaiJiangJie

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

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