Unverified Commit e7a731fa authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #1417 from jumpserver/dev

Dev
parents 283b1c1d 73f9f546
......@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
#
__version__ = "1.3.1"
__version__ = "1.3.2"
......@@ -116,7 +116,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
if not pk:
node = Node.root()
node = None
else:
node = get_object_or_404(Node, pk=pk)
return node
......@@ -126,7 +126,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
query_all = self.request.query_params.get("all")
query_assets = self.request.query_params.get('assets')
node = self.get_object()
if node == Node.root():
if node is None:
node = Node.root()
queryset.append(node)
if query_all:
children = node.get_all_children()
......
......@@ -51,7 +51,6 @@ def test_gateway_connectability(gateway):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.SSHClient()
proxy.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
......
......@@ -168,3 +168,49 @@ 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"),
min_value=6
)
# 大写字母
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')
)
......@@ -34,7 +34,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
def ldap_auth_on_changed(sender, enabled=True, **kwargs):
if enabled:
logger.debug("Enable LDAP auth")
if settings.AUTH_LDAP_BACKEND not in settings.AUTH_LDAP_BACKEND:
if settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
else:
......
......@@ -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="{% url 'settings:basic-setting' %}" 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>
</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
......@@ -82,7 +82,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
if form.is_valid():
form.save()
if "AUTH_LDAP" in form.cleaned_data:
ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"])
ldap_auth_enable.send(sender=self.__class__, enabled=form.cleaned_data["AUTH_LDAP"])
msg = _("Update setting successfully, please restart program")
messages.success(request, msg)
return redirect('settings:ldap-setting')
......@@ -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-07 11:34+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"
......@@ -17,19 +17,19 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: assets/api/node.py:106
#: assets/api/node.py:99
msgid "New node {}"
msgstr "新节点 {}"
#: assets/api/node.py:242
#: assets/api/node.py:234
msgid "更新节点资产硬件信息: {}"
msgstr ""
#: assets/api/node.py:255
#: assets/api/node.py:247
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:75 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:80
#: 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:71
#: 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:67
#: 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:223 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,15 +129,15 @@ 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
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:16
#: terminal/models.py:154 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
#: users/models/user.py:42 users/templates/users/_select_user_modal.html:13
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:12
#: users/models/user.py:49 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:63
#: users/templates/users/user_group_detail.html:55
#: users/templates/users/user_group_list.html:12
......@@ -155,7 +155,7 @@ msgstr "名称"
#: assets/templates/assets/system_user_list.html:27
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
#: users/forms.py:21 users/forms.py:30 users/models/authentication.py:45
#: users/models/user.py:40 users/templates/users/_select_user_modal.html:14
#: users/models/user.py:47 users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:56
#: users/templates/users/login_log_list.html:49
#: users/templates/users/user_detail.html:67
......@@ -171,16 +171,16 @@ 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"
msgstr "密码"
#: assets/forms/user.py:28 users/models/user.py:69
#: assets/forms/user.py:28 users/models/user.py:76
msgid "Private key"
msgstr "ssh私钥"
......@@ -202,11 +202,11 @@ msgid ""
"than 2 system user"
msgstr "高优先级的系统用户将会作为默认登录用户"
#: assets/models/asset.py:61 assets/models/domain.py:43
#: assets/models/asset.py:63 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:66 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:69 assets/templates/assets/asset_detail.html:97
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:67 assets/models/domain.py:48
#: assets/models/asset.py:76 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:85 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:87 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:91 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:93 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:95 assets/templates/assets/asset_detail.html:109
msgid "Serial number"
msgstr "序列号"
#: assets/models/asset.py:81
#: assets/models/asset.py:98
msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:82
#: assets/models/asset.py:99
msgid "CPU count"
msgstr "CPU数量"
#: assets/models/asset.py:83
#: assets/models/asset.py:100
msgid "CPU cores"
msgstr "CPU核数"
#: assets/models/asset.py:84 assets/templates/assets/asset_detail.html:89
#: assets/models/asset.py:102 assets/templates/assets/asset_detail.html:89
msgid "Memory"
msgstr "内存"
#: assets/models/asset.py:85
#: assets/models/asset.py:104
msgid "Disk total"
msgstr "硬盘大小"
#: assets/models/asset.py:86
#: assets/models/asset.py:106
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:101
#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:101
msgid "OS"
msgstr "操作系统"
#: assets/models/asset.py:89
#: assets/models/asset.py:111
msgid "OS version"
msgstr "系统版本"
#: assets/models/asset.py:90
#: assets/models/asset.py:113
msgid "OS arch"
msgstr "系统架构"
#: assets/models/asset.py:91
#: assets/models/asset.py:115
msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:93 assets/templates/assets/asset_create.html:33
#: assets/models/asset.py:119 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:121 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
......@@ -312,11 +312,11 @@ msgstr "标签管理"
#: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:38 perms/models.py:81
#: perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:83 users/templates/users/user_detail.html:107
#: users/models/user.py:90 users/templates/users/user_detail.html:111
msgid "Created by"
msgstr "创建者"
#: assets/models/asset.py:95 assets/models/cluster.py:26
#: assets/models/asset.py:124 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
......@@ -324,12 +324,12 @@ msgstr "创建者"
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63
#: perms/models.py:39 perms/models.py:82
#: perms/templates/perms/asset_permission_detail.html:94
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:15
#: users/templates/users/user_group_detail.html:63
msgid "Date created"
msgstr "创建日期"
#: assets/models/asset.py:96 assets/models/base.py:26
#: assets/models/asset.py:126 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
......@@ -342,11 +342,11 @@ msgstr "创建日期"
#: assets/templates/assets/system_user_list.html:33 common/models.py:30
#: ops/models/adhoc.py:42 perms/models.py:40 perms/models.py:83
#: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
#: users/models/user.py:75 users/templates/users/user_detail.html:119
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:13
#: users/models/user.py:82 users/templates/users/user_detail.html:123
#: 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:130
msgid "Comment"
msgstr "备注"
......@@ -366,7 +366,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:61
#: assets/models/cluster.py:22 users/models/user.py:68
#: users/templates/users/user_detail.html:76
msgid "Phone"
msgstr "手机"
......@@ -392,7 +392,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:13
#: users/models/user.py:330
#: users/models/user.py:343
msgid "System"
msgstr "系统"
......@@ -432,13 +432,13 @@ msgstr "默认资产组"
#: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33
#: 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:31 users/models/user.py:331
#: 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:362
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:188
#: 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,23 +651,24 @@ 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
#: users/templates/users/_user.html:47
#: 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_list.html:45
#: 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
......@@ -742,16 +744,16 @@ msgstr "测试"
#: users/templates/users/user_detail.html:25
#: 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_list.html:77
#: users/templates/users/user_profile.html:151
#: users/templates/users/user_profile.html:180
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
......@@ -766,8 +768,8 @@ msgstr "更新"
#: users/templates/users/user_detail.html:30
#: users/templates/users/user_group_detail.html:32
#: users/templates/users/user_group_list.html:45
#: users/templates/users/user_list.html:80
#: users/templates/users/user_list.html:84
#: users/templates/users/user_list.html:81
#: users/templates/users/user_list.html:85
msgid "Delete"
msgstr "删除"
......@@ -782,17 +784,17 @@ 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:638
#: 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
#: users/templates/users/user_detail.html:362
#: users/templates/users/user_detail.html:387
#: users/templates/users/user_detail.html:410
#: users/templates/users/user_detail.html:366
#: users/templates/users/user_detail.html:391
#: users/templates/users/user_detail.html:414
#: 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_list.html:200
#: users/templates/users/user_profile.html:222
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
......@@ -825,7 +827,7 @@ msgstr "比例"
#: terminal/templates/terminal/session_list.html:80
#: terminal/templates/terminal/terminal_list.html:36
#: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:28
#: users/templates/users/user_list.html:29
msgid "Action"
msgstr "动作"
......@@ -842,20 +844,20 @@ msgid "Disk"
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_detail.html:115
#: users/templates/users/user_profile.html:104
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_detail.html:134
#: users/templates/users/user_profile.html:142
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
......@@ -863,10 +865,10 @@ msgstr "快速修改"
#: perms/templates/perms/asset_permission_list.html:59
#: terminal/templates/terminal/terminal_list.html:34
#: users/templates/users/_select_user_modal.html:18
#: users/templates/users/user_detail.html:136
#: users/templates/users/user_detail.html:140
#: users/templates/users/user_granted_asset.html:46
#: users/templates/users/user_group_granted_asset.html:46
#: users/templates/users/user_list.html:27
#: users/templates/users/user_list.html:28
#: users/templates/users/user_profile.html:63
msgid "Active"
msgstr "激活中"
......@@ -880,124 +882,124 @@ msgid "Refresh"
msgstr "刷新"
#: assets/templates/assets/asset_detail.html:300
#: users/templates/users/user_detail.html:282
#: users/templates/users/user_detail.html:309
#: users/templates/users/user_detail.html:286
#: users/templates/users/user_detail.html:313
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
#: users/templates/users/user_list.html:37
#: assets/templates/assets/asset_list.html:100
#: users/templates/users/user_list.html:38
msgid "Delete selected"
msgstr "批量删除"
#: assets/templates/assets/asset_list.html:100
#: users/templates/users/user_list.html:38
#: assets/templates/assets/asset_list.html:101
#: users/templates/users/user_list.html:39
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
#: users/templates/users/user_list.html:39
#: assets/templates/assets/asset_list.html:103
#: users/templates/users/user_list.html:40
msgid "Deactive selected"
msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:103
#: users/templates/users/user_list.html:40
#: assets/templates/assets/asset_list.html:104
#: users/templates/users/user_list.html:41
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:633
#: assets/templates/assets/system_user_list.html:133
#: users/templates/users/user_detail.html:357
#: users/templates/users/user_detail.html:382
#: users/templates/users/user_detail.html:361
#: users/templates/users/user_detail.html:386
#: users/templates/users/user_group_list.html:81
#: users/templates/users/user_list.html:194
#: users/templates/users/user_list.html:195
msgid "Are you sure?"
msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:632
#: assets/templates/assets/asset_list.html:634
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:640
#: assets/templates/assets/asset_list.html:642
msgid "Asset Deleted."
msgstr "已被删除"
#: assets/templates/assets/asset_list.html:641
#: assets/templates/assets/asset_list.html:646
#: assets/templates/assets/asset_list.html:643
#: assets/templates/assets/asset_list.html:648
msgid "Asset Delete"
msgstr "删除"
#: assets/templates/assets/asset_list.html:645
#: assets/templates/assets/asset_list.html:647
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 "测试连接"
......@@ -1237,11 +1239,11 @@ msgstr "FTP日志"
msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查"
#: common/api.py:52
#: common/api.py:42
msgid "Test ldap success"
msgstr "连接LDAP成功"
#: common/api.py:90
#: common/api.py:80
msgid "Match {} s users"
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:191
msgid "Must contain capital letters"
msgstr "必须包含大写字母"
#: common/forms.py:193
msgid ""
"After opening, the user password changes and resets must contain uppercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: common/forms.py:199
msgid "Must contain lowercase letters"
msgstr "必须包含小写字母"
#: common/forms.py:200
msgid ""
"After opening, the user password changes and resets must contain lowercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: common/forms.py:206
msgid "Must contain numeric characters"
msgstr "必须包含数字字符"
#: common/forms.py:207
msgid ""
"After opening, the user password changes and resets must contain numeric "
"characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: common/forms.py:213
msgid "Must contain special characters"
msgstr "必须包含特殊字符"
#: common/forms.py:214
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 "更新设置成功, 请手动重启程序"
......@@ -1736,9 +1813,9 @@ msgstr "选择用户"
#: perms/forms.py:34 perms/models.py:31 perms/models.py:77
#: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14
#: users/models/group.py:25 users/models/user.py:48
#: users/models/group.py:23 users/models/user.py:55
#: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:188
#: users/templates/users/user_detail.html:192
#: users/templates/users/user_list.html:26
msgid "User group"
msgstr "用户组"
......@@ -1753,8 +1830,8 @@ 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/models/user.py:87 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:112
msgid "Date expired"
msgstr "失效日期"
......@@ -1791,7 +1868,7 @@ msgid "Add node to this permission"
msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:125
#: users/templates/users/user_detail.html:205
#: users/templates/users/user_detail.html:209
msgid "Join"
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:344
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:277 users/views/login.py:335 users/views/user.py:66
#: users/views/user.py:81 users/views/user.py:103 users/views/user.py:174
#: users/views/user.py:329 users/views/user.py:381 users/views/user.py:416
msgid "Users"
msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:65
#: templates/_nav.html:13 users/views/user.py:67
msgid "User list"
msgstr "用户列表"
......@@ -2261,7 +2338,7 @@ msgstr ""
msgid "MFA code"
msgstr "MFA 验证码"
#: users/forms.py:49 users/models/user.py:52
#: users/forms.py:49 users/models/user.py:59
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:25
......@@ -2281,7 +2358,7 @@ msgstr ""
msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里"
#: users/forms.py:72 users/templates/users/user_detail.html:196
#: users/forms.py:72 users/templates/users/user_detail.html:200
msgid "Join user groups"
msgstr "添加到用户组"
......@@ -2289,7 +2366,7 @@ msgstr "添加到用户组"
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
#: users/forms.py:87 users/forms.py:220 users/serializers.py:45
#: users/forms.py:87 users/forms.py:220 users/serializers.py:48
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
......@@ -2306,7 +2383,7 @@ msgstr ""
msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全."
#: users/forms.py:142 users/models/user.py:64
#: users/forms.py:142 users/models/user.py:71
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
......@@ -2354,9 +2431,9 @@ msgstr "自动配置并下载SSH密钥"
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
#: users/forms.py:231 users/models/user.py:72
#: users/forms.py:231 users/models/user.py:79
#: 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
......@@ -2387,43 +2464,49 @@ msgstr "Agent"
msgid "Date login"
msgstr "登录日期"
#: users/models/user.py:29 users/models/user.py:326
#: users/models/user.py:30 users/models/user.py:339
msgid "Administrator"
msgstr "管理员"
#: users/models/user.py:31
#: users/models/user.py:32
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/models/user.py:35 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:166
msgid "Disable"
msgstr "禁用"
#: users/models/user.py:35 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:163
#: users/models/user.py:36 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:170
msgid "Enable"
msgstr "启用"
#: users/models/user.py:36 users/templates/users/user_profile.html:88
#: users/models/user.py:37 users/templates/users/user_profile.html:88
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:44 users/templates/users/user_detail.html:71
#: users/models/user.py:51 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59
msgid "Email"
msgstr "邮件"
#: users/models/user.py:55
#: users/models/user.py:62
msgid "Avatar"
msgstr "头像"
#: users/models/user.py:58 users/templates/users/user_detail.html:82
#: users/models/user.py:65 users/templates/users/user_detail.html:82
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:329
#: users/models/user.py:94 users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:27
#: users/templates/users/user_profile.html:100
msgid "Source"
msgstr "用户来源"
#: users/models/user.py:342
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
......@@ -2539,22 +2622,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:352 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:81
msgid "Create user"
msgstr "创建用户"
......@@ -2563,7 +2658,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:175
msgid "User detail"
msgstr "用户详情"
......@@ -2582,63 +2677,63 @@ msgstr "强制启用"
msgid "Disabled"
msgstr "禁用"
#: users/templates/users/user_detail.html:115
#: users/templates/users/user_profile.html:101
#: users/templates/users/user_detail.html:119
#: users/templates/users/user_profile.html:108
msgid "Last login"
msgstr "最后登录"
#: users/templates/users/user_detail.html:151
#: users/templates/users/user_detail.html:155
msgid "Force enabled MFA"
msgstr "强制启用MFA"
#: users/templates/users/user_detail.html:166
#: users/templates/users/user_detail.html:170
msgid "Send reset password mail"
msgstr "发送重置密码邮件"
#: users/templates/users/user_detail.html:169
#: users/templates/users/user_detail.html:177
#: users/templates/users/user_detail.html:173
#: users/templates/users/user_detail.html:181
msgid "Send"
msgstr "发送"
#: users/templates/users/user_detail.html:174
#: users/templates/users/user_detail.html:178
msgid "Send reset ssh key mail"
msgstr "发送重置密钥邮件"
#: users/templates/users/user_detail.html:291
#: users/templates/users/user_detail.html:295
msgid "Goto profile page enable MFA"
msgstr "请去个人信息页面启用自己的MFA"
#: users/templates/users/user_detail.html:347
#: users/templates/users/user_detail.html:351
msgid "An e-mail has been sent to the user`s mailbox."
msgstr "已发送邮件到用户邮箱"
#: users/templates/users/user_detail.html:358
#: users/templates/users/user_detail.html:362
msgid "This will reset the user password and send a reset mail"
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
#: users/templates/users/user_detail.html:372
#: users/templates/users/user_detail.html:376
msgid ""
"The reset-ssh-public-key E-mail has been sent successfully. Please inform "
"the user to update his new ssh public key."
msgstr "重设密钥邮件将会发送到用户邮箱"
#: users/templates/users/user_detail.html:373
#: users/templates/users/user_detail.html:377
msgid "Reset SSH public key"
msgstr "重置SSH密钥"
#: users/templates/users/user_detail.html:383
#: users/templates/users/user_detail.html:387
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_detail.html:404
#: users/templates/users/user_profile.html:211
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_detail.html:409
#: users/templates/users/user_profile.html:212
#: users/templates/users/user_profile.html:217
msgid "User SSH public key update"
msgstr "ssh密钥"
......@@ -2677,45 +2772,49 @@ msgstr "用户组删除"
msgid "UserGroup Deleting failed."
msgstr "用户组删除失败"
#: users/templates/users/user_list.html:195
#: users/templates/users/user_list.html:196
msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:203
#: users/templates/users/user_list.html:204
msgid "User Deleted."
msgstr "已被删除"
#: users/templates/users/user_list.html:204
#: users/templates/users/user_list.html:209
#: users/templates/users/user_list.html:205
#: users/templates/users/user_list.html:210
msgid "User Delete"
msgstr "删除"
#: users/templates/users/user_list.html:208
#: users/templates/users/user_list.html:209
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:116 users/views/user.py:204
#: users/views/user.py:258
msgid "User groups"
msgstr "用户组"
#: users/templates/users/user_profile.html:141
#: users/templates/users/user_profile.html:148
msgid "Update password"
msgstr "更改密码"
#: users/templates/users/user_profile.html:149
#: users/templates/users/user_profile.html:156
msgid "Update MFA settings"
msgstr "更改MFA设置"
#: users/templates/users/user_profile.html:170
#: users/templates/users/user_profile.html:177
msgid "Update SSH public key"
msgstr "更改SSH密钥"
#: users/templates/users/user_profile.html:178
#: users/templates/users/user_profile.html:185
msgid "Reset public key and download"
msgstr "重置并下载SSH密钥"
#: users/templates/users/user_profile.html:208
#: users/templates/users/user_profile.html:215
msgid "Failed to update SSH public key."
msgstr "更新密钥失败"
......@@ -2735,15 +2834,21 @@ msgstr "更新密钥"
msgid "Or reset by server"
msgstr "或者重置并下载密钥"
#: users/templates/users/user_update.html:4 users/views/user.py:99
#: users/templates/users/user_pubkey_update.html:94
msgid ""
"The new public key has been set successfully, Please download the "
"corresponding private key."
msgstr "新的公钥已设置成功,请下载对应的私钥"
#: users/templates/users/user_update.html:4 users/views/user.py:104
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 +2893,7 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:78
#: users/utils.py:82
#, python-format
msgid ""
"\n"
......@@ -2832,11 +2937,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 +2966,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 +2994,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:128 users/views/user.py:501 users/views/user.py:526
msgid "MFA code invalid"
msgstr "MFA码认证失败"
#: users/views/login.py:151
#: users/views/login.py:154
msgid "Logout success"
msgstr "退出登录成功"
#: users/views/login.py:152
#: users/views/login.py:155
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:168
#: users/views/login.py:171
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:181
#: users/views/login.py:184
msgid "Send reset password message"
msgstr "发送重置密码邮件"
#: users/views/login.py:182
#: users/views/login.py:185
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:198
msgid "Reset password success"
msgstr "重置密码成功"
#: users/views/login.py:196
#: users/views/login.py:199
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:213 users/views/login.py:226
#: users/views/login.py:220 users/views/login.py:233
msgid "Token invalid or expired"
msgstr "Token错误或失效"
#: users/views/login.py:222
#: users/views/login.py:229
msgid "Password not same"
msgstr "密码不一致"
#: users/views/login.py:263
#: users/views/login.py:239 users/views/user.py:116 users/views/user.py:399
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
#: users/views/login.py:277
msgid "First login"
msgstr "首次登陆"
#: users/views/login.py:322
#: users/views/login.py:336
msgid "Login log list"
msgstr "登录日志"
#: users/views/user.py:109
#: users/views/user.py:128
msgid "Bulk update user success"
msgstr "批量更新用户成功"
#: users/views/user.py:214
#: users/views/user.py:233
msgid "Invalid file."
msgstr "文件不合法"
#: users/views/user.py:311
#: users/views/user.py:330
msgid "User granted assets"
msgstr "用户授权资产"
#: users/views/user.py:340
#: users/views/user.py:363
msgid "Profile setting"
msgstr "个人信息设置"
#: users/views/user.py:358
#: users/views/user.py:382
msgid "Password update"
msgstr "密码更新"
#: users/views/user.py:380
#: users/views/user.py:417
msgid "Public key update"
msgstr "密钥更新"
#: users/views/user.py:421
#: users/views/user.py:458
msgid "Password invalid"
msgstr "用户名或密码无效"
#: users/views/user.py:515
#: users/views/user.py:552
msgid "MFA enable success"
msgstr "MFA 绑定成功"
#: users/views/user.py:516
#: users/views/user.py:553
msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:518
#: users/views/user.py:555
msgid "MFA disable success"
msgstr "MFA 解绑成功"
#: users/views/user.py:519
#: users/views/user.py:556
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',
......
......@@ -86,6 +86,7 @@ class JMSInventory(BaseInventory):
gateway = asset.domain.random_gateway()
proxy_command_list = [
"ssh", "-p", str(gateway.port),
"-o", "StrictHostKeyChecking=no",
"{}@{}".format(gateway.username, gateway.ip),
"-W", "%h:%p", "-q",
]
......
......@@ -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 %}
......
<div class="footer fixed">
<div class="pull-right">
Version <strong>1.3.1-{% include '_build.html' %}</strong> GPLv2.
Version <strong>1.3.2-{% include '_build.html' %}</strong> GPLv2.
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div>
<div>
......
......@@ -16,13 +16,14 @@ class UserLoginForm(AuthenticationForm):
max_length=128, strip=False
)
def confirm_login_allowed(self, user):
if not user.is_staff:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',)
class UserLoginCaptchaForm(AuthenticationForm):
username = forms.CharField(label=_('Username'), max_length=100)
password = forms.CharField(
label=_('Password'), widget=forms.PasswordInput,
max_length=128, strip=False
)
class UserLoginCaptchaForm(UserLoginForm):
captcha = CaptchaField()
......@@ -72,7 +73,7 @@ class UserCreateUpdateForm(forms.ModelForm):
'data-placeholder': _('Join user groups')
}
),
'otp_level': forms.RadioSelect()
'otp_level': forms.RadioSelect(),
}
def clean_public_key(self):
......
......@@ -14,6 +14,7 @@ from django.utils import timezone
from django.shortcuts import reverse
from common.utils import get_signer, date_expired_default
from common.models import Setting
__all__ = ['User']
......@@ -35,6 +36,12 @@ class User(AbstractUser):
(1, _('Enable')),
(2, _("Force enable")),
)
SOURCE_LOCAL = 'local'
SOURCE_LDAP = 'ldap'
SOURCE_CHOICES = (
(SOURCE_LOCAL, 'Local'),
(SOURCE_LDAP, 'LDAP/AD'),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField(
max_length=128, unique=True, verbose_name=_('Username')
......@@ -82,6 +89,10 @@ class User(AbstractUser):
created_by = models.CharField(
max_length=30, default='', verbose_name=_('Created by')
)
source = models.CharField(
max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES,
verbose_name=_('Source')
)
def __str__(self):
return '{0.name}({0.username})'.format(self)
......@@ -248,14 +259,17 @@ class User(AbstractUser):
@property
def otp_enabled(self):
return self.otp_level > 0
return self.otp_force_enabled or self.otp_level > 0
@property
def otp_force_enabled(self):
mfa_setting = Setting.objects.filter(name='SECURITY_MFA_AUTH').first()
if mfa_setting and mfa_setting.cleaned_value:
return True
return self.otp_level == 2
def enable_otp(self):
if not self.otp_force_enabled:
if not self.otp_level == 2:
self.otp_level = 1
def force_enable_otp(self):
......@@ -275,6 +289,7 @@ class User(AbstractUser):
'is_superuser': self.is_superuser,
'role': self.get_role_display(),
'groups': [group.name for group in self.groups.all()],
'source': self.get_source_display(),
'wechat': self.wechat,
'phone': self.phone,
'otp_level': self.otp_level,
......
......@@ -26,7 +26,10 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
def get_field_names(self, declared_fields, info):
fields = super(UserSerializer, self).get_field_names(declared_fields, info)
fields.extend(['groups_display', 'get_role_display', 'is_valid'])
fields.extend([
'groups_display', 'get_role_display',
'get_source_display', 'is_valid'
])
return fields
@staticmethod
......
......@@ -2,6 +2,7 @@
#
from django.dispatch import receiver
from django_auth_ldap.backend import populate_user
# from django.db.models.signals import post_save
from common.utils import get_logger
......@@ -28,3 +29,11 @@ def on_user_create(sender, user=None, **kwargs):
logger.info(" - Sending welcome mail ...".format(user.name))
if user.email:
send_user_created_mail(user)
@receiver(populate_user)
def on_ldap_create_user(sender, user, ldap_user, **kwargs):
if user:
user.source = user.SOURCE_LDAP
user.save()
......@@ -48,6 +48,7 @@
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
......
......@@ -138,8 +138,9 @@
}
}).on('click', '#btn-reset-pubkey', function () {
var the_url = '{% url "users:user-pubkey-generate" %}';
window.open(the_url, "_blank")
})
window.open(the_url, "_blank");
$('#fl_form').submit();
})
</script>
{% endblock %}
......@@ -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>
......@@ -99,6 +99,10 @@
{% endif %}
</b></td>
</tr>
<tr>
<td>{% trans 'Source' %}:</td>
<td><b>{{ user_object.get_source_display }}</b></td>
</tr>
<tr>
<td>{% trans 'Date expired' %}:</td>
<td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td>
......
......@@ -24,6 +24,7 @@
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Role' %}</th>
<th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'Source' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
......@@ -65,14 +66,14 @@ function initTable() {
var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData;
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 5, createdCell: function (td, cellData) {
{targets: 6, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
{targets: 7, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
var del_btn = "";
......@@ -90,7 +91,7 @@ function initTable() {
ajax_url: '{% url "api-users:user-list" %}',
columns: [
{data: "id"}, {data: "name" }, {data: "username" }, {data: "get_role_display" },
{data: "groups_display" }, {data: "is_valid" }, {data: "id" }
{data: "groups_display" }, {data: "get_source_display" }, {data: "is_valid" }, {data: "id" }
],
op_html: $('#actions').html()
};
......
......@@ -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,8 +91,15 @@
{% else %}
{% trans 'Disable' %}
{% endif %}
{% if mfa_setting %}
( {% trans 'Administrator Settings force MFA login' %} )
{% endif %}
</td>
</tr>
<tr>
<td class="text-navy">{% trans 'Source' %}</td>
<td>{{ user.get_source_display }}</td>
</tr>
<tr>
<td class="text-navy">{% trans 'Date joined' %}</td>
<td>{{ user.date_joined|date:"Y-m-d H:i:s" }}</td>
......
......@@ -67,7 +67,7 @@
<div class="form-group">
<label class="control-label col-sm-2 col-lg-2" style="padding-top: 0">{% trans 'Or reset by server' %}</label>
<div class=" col-sm-9 col-lg-9 ">
<a href="{% url 'users:user-pubkey-generate' %}">{% trans 'Reset' %}</a>
<a id="reset_pubkey" href="{% url 'users:user-pubkey-generate' %}">{% trans 'Reset' %}</a>
</div>
</div>
<div class="hr-line-dashed"></div>
......@@ -89,5 +89,10 @@
{% block custom_foot_js %}
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script>
<script>
$(document).ready(function () {
}).on('click', '#reset_pubkey', function () {
var message = "{% trans 'The new public key has been set successfully, Please download the corresponding private key.' %}";
toastr.success(message)
})
</script>
{% endblock %}
......@@ -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
......@@ -82,10 +83,10 @@ class UserLoginView(FormView):
user = get_user_or_tmp_user(self.request)
if user.otp_enabled and user.otp_secret_key:
# 1,2 & T
# 1,2,mfa_setting & T
return reverse('users:login-otp')
elif user.otp_enabled and not user.otp_secret_key:
# 1,2 & F
# 1,2,mfa_setting & F
return reverse('users:user-otp-enable-authentication')
elif not user.otp_enabled:
# 0 & T,F
......@@ -211,6 +212,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 +232,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,29 @@ 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')
if not password:
return super().form_valid(form)
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 +338,10 @@ 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 +375,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 +389,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'
......
......@@ -51,11 +51,6 @@ class Config:
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
REDIS_PASSWORD = ''
BROKER_URL = 'redis://%(password)s%(host)s:%(port)s/3' % {
'password': REDIS_PASSWORD,
'host': REDIS_HOST,
'port': REDIS_PORT,
}
def __init__(self):
pass
......
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