Commit 0a2ff83c authored by ibuler's avatar ibuler

Merge remote-tracking branch 'origin/dev' into dev

parents 7276bd0b 73f9f546
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
__version__ = "1.3.1" __version__ = "1.3.2"
...@@ -116,7 +116,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): ...@@ -116,7 +116,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
def get_object(self): def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id') pk = self.kwargs.get('pk') or self.request.query_params.get('id')
if not pk: if not pk:
node = Node.root() node = None
else: else:
node = get_object_or_404(Node, pk=pk) node = get_object_or_404(Node, pk=pk)
return node return node
...@@ -126,7 +126,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): ...@@ -126,7 +126,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
query_all = self.request.query_params.get("all") query_all = self.request.query_params.get("all")
query_assets = self.request.query_params.get('assets') query_assets = self.request.query_params.get('assets')
node = self.get_object() node = self.get_object()
if node == Node.root(): if node is None:
node = Node.root()
queryset.append(node) queryset.append(node)
if query_all: if query_all:
children = node.get_all_children() children = node.get_all_children()
......
...@@ -51,7 +51,6 @@ def test_gateway_connectability(gateway): ...@@ -51,7 +51,6 @@ def test_gateway_connectability(gateway):
client = paramiko.SSHClient() client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.SSHClient() proxy = paramiko.SSHClient()
proxy.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try: try:
......
...@@ -168,3 +168,49 @@ class TerminalSettingForm(BaseForm): ...@@ -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): ...@@ -34,7 +34,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
def ldap_auth_on_changed(sender, enabled=True, **kwargs): def ldap_auth_on_changed(sender, enabled=True, **kwargs):
if enabled: if enabled:
logger.debug("Enable LDAP auth") 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) settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
else: else:
......
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
<li> <li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a> <a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
......
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
<li> <li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a> <a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
......
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
<li> <li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a> <a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
......
{% extends 'base.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% 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 @@ ...@@ -27,6 +27,9 @@
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i <a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a> class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
...@@ -39,6 +42,7 @@ ...@@ -39,6 +42,7 @@
</div> </div>
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}
<h3>{% trans "Basic setting" %}</h3> <h3>{% trans "Basic setting" %}</h3>
{% for field in form %} {% for field in form %}
{% if not field.field|is_bool_field %} {% if not field.field|is_bool_field %}
...@@ -60,6 +64,7 @@ ...@@ -60,6 +64,7 @@
{% endfor %} {% endfor %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans "Command storage" %}</h3> <h3>{% trans "Command storage" %}</h3>
<table class="table table-hover " id="task-history-list-table"> <table class="table table-hover " id="task-history-list-table">
<thead> <thead>
......
...@@ -11,4 +11,5 @@ urlpatterns = [ ...@@ -11,4 +11,5 @@ urlpatterns = [
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'), url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'),
] ]
...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _ ...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm TerminalSettingForm, SecuritySettingForm
from .mixins import AdminUserRequiredMixin from .mixins import AdminUserRequiredMixin
from .signals import ldap_auth_enable from .signals import ldap_auth_enable
...@@ -82,7 +82,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -82,7 +82,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
if form.is_valid(): if form.is_valid():
form.save() form.save()
if "AUTH_LDAP" in form.cleaned_data: 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") msg = _("Update setting successfully, please restart program")
messages.success(request, msg) messages.success(request, msg)
return redirect('settings:ldap-setting') return redirect('settings:ldap-setting')
...@@ -122,3 +122,27 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -122,3 +122,27 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class SecuritySettingView(AdminUserRequiredMixin, TemplateView):
form_class = SecuritySettingForm
template_name = "common/security_setting.html"
def get_context_data(self, **kwargs):
context = {
'app': _('Settings'),
'action': _('Security setting'),
'form': self.form_class(),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
msg = _("Update setting successfully, please restart program")
messages.success(request, msg)
return redirect('settings:security-setting')
else:
context = self.get_context_data()
context.update({"form": form})
return render(request, self.template_name, context)
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-05-25 18:11+0800\n" "POT-Creation-Date: 2018-06-07 11:34+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n"
...@@ -17,19 +17,19 @@ msgstr "" ...@@ -17,19 +17,19 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: assets/api/node.py:106 #: assets/api/node.py:99
msgid "New node {}" msgid "New node {}"
msgstr "新节点 {}" msgstr "新节点 {}"
#: assets/api/node.py:242 #: assets/api/node.py:234
msgid "更新节点资产硬件信息: {}" msgid "更新节点资产硬件信息: {}"
msgstr "" msgstr ""
#: assets/api/node.py:255 #: assets/api/node.py:247
msgid "测试节点下资产是否可连接: {}" msgid "测试节点下资产是否可连接: {}"
msgstr "" msgstr ""
#: assets/forms/asset.py:24 assets/models/asset.py:66 assets/models/user.py:103 #: assets/forms/asset.py:24 assets/models/asset.py:75 assets/models/user.py:103
#: assets/templates/assets/asset_detail.html:183 #: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/asset_detail.html:191 #: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/system_user_detail.html:175 perms/models.py:33 #: assets/templates/assets/system_user_detail.html:175 perms/models.py:33
...@@ -37,7 +37,7 @@ msgid "Nodes" ...@@ -37,7 +37,7 @@ msgid "Nodes"
msgstr "节点管理" msgstr "节点管理"
#: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109 #: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109
#: assets/forms/asset.py:113 assets/models/asset.py:70 #: assets/forms/asset.py:113 assets/models/asset.py:80
#: assets/models/cluster.py:19 assets/models/user.py:72 #: assets/models/cluster.py:19 assets/models/user.py:72
#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25 #: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25
msgid "Admin user" msgid "Admin user"
...@@ -46,14 +46,14 @@ msgstr "管理用户" ...@@ -46,14 +46,14 @@ msgstr "管理用户"
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125 #: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125
#: assets/templates/assets/asset_create.html:35 #: assets/templates/assets/asset_create.html:35
#: assets/templates/assets/asset_create.html:37 #: assets/templates/assets/asset_create.html:37
#: assets/templates/assets/asset_list.html:74 #: assets/templates/assets/asset_list.html:75
#: assets/templates/assets/asset_update.html:40 #: assets/templates/assets/asset_update.html:40
#: assets/templates/assets/asset_update.html:42 #: assets/templates/assets/asset_update.html:42
#: assets/templates/assets/user_asset_list.html:34 #: assets/templates/assets/user_asset_list.html:34
msgid "Label" msgid "Label"
msgstr "标签" msgstr "标签"
#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:65 #: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:71
#: assets/models/domain.py:46 #: assets/models/domain.py:46
msgid "Domain" msgid "Domain"
msgstr "网域" msgstr "网域"
...@@ -90,7 +90,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, ...@@ -90,7 +90,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
msgid "Select assets" msgid "Select assets"
msgstr "选择资产" msgstr "选择资产"
#: assets/forms/asset.py:105 assets/models/asset.py:63 #: assets/forms/asset.py:105 assets/models/asset.py:67
#: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53 #: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/domain_gateway_list.html:58
...@@ -99,7 +99,7 @@ msgid "Port" ...@@ -99,7 +99,7 @@ msgid "Port"
msgstr "端口" msgstr "端口"
#: assets/forms/domain.py:14 assets/forms/label.py:13 #: assets/forms/domain.py:14 assets/forms/label.py:13
#: assets/models/asset.py:183 assets/templates/assets/admin_user_list.html:25 #: assets/models/asset.py:223 assets/templates/assets/admin_user_list.html:25
#: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:15 #: assets/templates/assets/domain_list.html:15
#: assets/templates/assets/label_list.html:16 #: assets/templates/assets/label_list.html:16
...@@ -129,15 +129,15 @@ msgstr "资产" ...@@ -129,15 +129,15 @@ msgstr "资产"
#: assets/templates/assets/label_list.html:14 #: assets/templates/assets/label_list.html:14
#: assets/templates/assets/system_user_detail.html:58 #: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:26 common/models.py:26 #: assets/templates/assets/system_user_list.html:26 common/models.py:26
#: common/templates/common/terminal_setting.html:67 #: common/templates/common/terminal_setting.html:72
#: common/templates/common/terminal_setting.html:85 ops/models/adhoc.py:36 #: common/templates/common/terminal_setting.html:90 ops/models/adhoc.py:36
#: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35 #: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35
#: perms/models.py:29 perms/templates/perms/asset_permission_detail.html:62 #: perms/models.py:29 perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:53 #: perms/templates/perms/asset_permission_list.html:53
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:16 #: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:16
#: terminal/models.py:154 terminal/templates/terminal/terminal_detail.html:43 #: terminal/models.py:154 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:12
#: users/models/user.py:42 users/templates/users/_select_user_modal.html:13 #: users/models/user.py:49 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:63 #: users/templates/users/user_detail.html:63
#: users/templates/users/user_group_detail.html:55 #: users/templates/users/user_group_detail.html:55
#: users/templates/users/user_group_list.html:12 #: users/templates/users/user_group_list.html:12
...@@ -155,7 +155,7 @@ msgstr "名称" ...@@ -155,7 +155,7 @@ msgstr "名称"
#: assets/templates/assets/system_user_list.html:27 #: assets/templates/assets/system_user_list.html:27
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
#: users/forms.py:21 users/forms.py:30 users/models/authentication.py:45 #: 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.html:56
#: users/templates/users/login_log_list.html:49 #: users/templates/users/login_log_list.html:49
#: users/templates/users/user_detail.html:67 #: users/templates/users/user_detail.html:67
...@@ -171,16 +171,16 @@ msgstr "密码或密钥密码" ...@@ -171,16 +171,16 @@ msgstr "密码或密钥密码"
#: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113 #: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113
#: users/forms.py:15 users/forms.py:23 users/forms.py:32 users/forms.py:44 #: users/forms.py:15 users/forms.py:23 users/forms.py:32 users/forms.py:44
#: users/templates/users/login.html:59 #: users/templates/users/login.html:59
#: users/templates/users/reset_password.html:52 #: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10 #: users/templates/users/user_create.html:10
#: users/templates/users/user_password_authentication.html:14 #: users/templates/users/user_password_authentication.html:14
#: users/templates/users/user_password_update.html:40 #: users/templates/users/user_password_update.html:42
#: users/templates/users/user_profile_update.html:40 #: users/templates/users/user_profile_update.html:40
#: users/templates/users/user_pubkey_update.html:40 #: users/templates/users/user_pubkey_update.html:40
msgid "Password" msgid "Password"
msgstr "密码" msgstr "密码"
#: assets/forms/user.py:28 users/models/user.py:69 #: assets/forms/user.py:28 users/models/user.py:76
msgid "Private key" msgid "Private key"
msgstr "ssh私钥" msgstr "ssh私钥"
...@@ -202,11 +202,11 @@ msgid "" ...@@ -202,11 +202,11 @@ msgid ""
"than 2 system user" "than 2 system user"
msgstr "高优先级的系统用户将会作为默认登录用户" msgstr "高优先级的系统用户将会作为默认登录用户"
#: assets/models/asset.py:61 assets/models/domain.py:43 #: assets/models/asset.py:63 assets/models/domain.py:43
#: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61 #: assets/templates/assets/asset_detail.html:61
#: assets/templates/assets/asset_list.html:86 #: assets/templates/assets/asset_list.html:87
#: assets/templates/assets/domain_gateway_list.html:57 #: assets/templates/assets/domain_gateway_list.html:57
#: assets/templates/assets/system_user_asset.html:50 #: assets/templates/assets/system_user_asset.html:50
#: assets/templates/assets/user_asset_list.html:46 common/forms.py:144 #: assets/templates/assets/user_asset_list.html:46 common/forms.py:144
...@@ -217,10 +217,10 @@ msgstr "高优先级的系统用户将会作为默认登录用户" ...@@ -217,10 +217,10 @@ msgstr "高优先级的系统用户将会作为默认登录用户"
msgid "IP" msgid "IP"
msgstr "IP" msgstr "IP"
#: assets/models/asset.py:62 assets/templates/assets/_asset_list_modal.html:45 #: assets/models/asset.py:66 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/admin_user_assets.html:51 #: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/asset_detail.html:57 #: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_list.html:85 #: assets/templates/assets/asset_list.html:86
#: assets/templates/assets/system_user_asset.html:49 #: assets/templates/assets/system_user_asset.html:49
#: assets/templates/assets/user_asset_list.html:45 common/forms.py:143 #: assets/templates/assets/user_asset_list.html:45 common/forms.py:143
#: perms/templates/perms/asset_permission_asset.html:54 #: perms/templates/perms/asset_permission_asset.html:54
...@@ -229,82 +229,82 @@ msgstr "IP" ...@@ -229,82 +229,82 @@ msgstr "IP"
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: assets/models/asset.py:64 assets/templates/assets/asset_detail.html:97 #: assets/models/asset.py:69 assets/templates/assets/asset_detail.html:97
msgid "Platform" msgid "Platform"
msgstr "系统平台" msgstr "系统平台"
#: assets/models/asset.py:67 assets/models/domain.py:48 #: assets/models/asset.py:76 assets/models/domain.py:48
#: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105 #: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105
msgid "Is active" msgid "Is active"
msgstr "激活" msgstr "激活"
#: assets/models/asset.py:73 assets/templates/assets/asset_detail.html:65 #: assets/models/asset.py:85 assets/templates/assets/asset_detail.html:65
msgid "Public IP" msgid "Public IP"
msgstr "公网IP" msgstr "公网IP"
#: assets/models/asset.py:74 assets/templates/assets/asset_detail.html:113 #: assets/models/asset.py:87 assets/templates/assets/asset_detail.html:113
msgid "Asset number" msgid "Asset number"
msgstr "资产编号" msgstr "资产编号"
#: assets/models/asset.py:77 assets/templates/assets/asset_detail.html:77 #: assets/models/asset.py:91 assets/templates/assets/asset_detail.html:77
msgid "Vendor" msgid "Vendor"
msgstr "制造商" msgstr "制造商"
#: assets/models/asset.py:78 assets/templates/assets/asset_detail.html:81 #: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:81
msgid "Model" msgid "Model"
msgstr "型号" msgstr "型号"
#: assets/models/asset.py:79 assets/templates/assets/asset_detail.html:109 #: assets/models/asset.py:95 assets/templates/assets/asset_detail.html:109
msgid "Serial number" msgid "Serial number"
msgstr "序列号" msgstr "序列号"
#: assets/models/asset.py:81 #: assets/models/asset.py:98
msgid "CPU model" msgid "CPU model"
msgstr "CPU型号" msgstr "CPU型号"
#: assets/models/asset.py:82 #: assets/models/asset.py:99
msgid "CPU count" msgid "CPU count"
msgstr "CPU数量" msgstr "CPU数量"
#: assets/models/asset.py:83 #: assets/models/asset.py:100
msgid "CPU cores" msgid "CPU cores"
msgstr "CPU核数" msgstr "CPU核数"
#: assets/models/asset.py:84 assets/templates/assets/asset_detail.html:89 #: assets/models/asset.py:102 assets/templates/assets/asset_detail.html:89
msgid "Memory" msgid "Memory"
msgstr "内存" msgstr "内存"
#: assets/models/asset.py:85 #: assets/models/asset.py:104
msgid "Disk total" msgid "Disk total"
msgstr "硬盘大小" msgstr "硬盘大小"
#: assets/models/asset.py:86 #: assets/models/asset.py:106
msgid "Disk info" msgid "Disk info"
msgstr "硬盘信息" 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" msgid "OS"
msgstr "操作系统" msgstr "操作系统"
#: assets/models/asset.py:89 #: assets/models/asset.py:111
msgid "OS version" msgid "OS version"
msgstr "系统版本" msgstr "系统版本"
#: assets/models/asset.py:90 #: assets/models/asset.py:113
msgid "OS arch" msgid "OS arch"
msgstr "系统架构" msgstr "系统架构"
#: assets/models/asset.py:91 #: assets/models/asset.py:115
msgid "Hostname raw" msgid "Hostname raw"
msgstr "主机名原始" 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_detail.html:220
#: assets/templates/assets/asset_update.html:38 templates/_nav.html:27 #: assets/templates/assets/asset_update.html:38 templates/_nav.html:27
msgid "Labels" msgid "Labels"
msgstr "标签管理" msgstr "标签管理"
#: assets/models/asset.py:94 assets/models/base.py:29 #: assets/models/asset.py:121 assets/models/base.py:29
#: assets/models/cluster.py:28 assets/models/group.py:21 #: assets/models/cluster.py:28 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:117 #: assets/templates/assets/asset_detail.html:117
...@@ -312,11 +312,11 @@ msgstr "标签管理" ...@@ -312,11 +312,11 @@ msgstr "标签管理"
#: assets/templates/assets/system_user_detail.html:96 #: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:38 perms/models.py:81 #: ops/templates/ops/adhoc_detail.html:86 perms/models.py:38 perms/models.py:81
#: perms/templates/perms/asset_permission_detail.html:98 #: 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" msgid "Created by"
msgstr "创建者" 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/domain.py:20 assets/models/group.py:22
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/domain_detail.html:68
...@@ -324,12 +324,12 @@ msgstr "创建者" ...@@ -324,12 +324,12 @@ msgstr "创建者"
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63
#: perms/models.py:39 perms/models.py:82 #: perms/models.py:39 perms/models.py:82
#: perms/templates/perms/asset_permission_detail.html:94 #: 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 #: users/templates/users/user_group_detail.html:63
msgid "Date created" msgid "Date created"
msgstr "创建日期" 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/cluster.py:29 assets/models/domain.py:18
#: assets/models/domain.py:47 assets/models/group.py:23 #: assets/models/domain.py:47 assets/models/group.py:23
#: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72 #: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72
...@@ -342,11 +342,11 @@ msgstr "创建日期" ...@@ -342,11 +342,11 @@ msgstr "创建日期"
#: assets/templates/assets/system_user_list.html:33 common/models.py:30 #: 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 #: 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 #: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 #: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:13
#: users/models/user.py:75 users/templates/users/user_detail.html:119 #: 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_detail.html:67
#: users/templates/users/user_group_list.html:14 #: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:123 #: users/templates/users/user_profile.html:130
msgid "Comment" msgid "Comment"
msgstr "备注" msgstr "备注"
...@@ -366,7 +366,7 @@ msgstr "带宽" ...@@ -366,7 +366,7 @@ msgstr "带宽"
msgid "Contact" msgid "Contact"
msgstr "联系人" 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 #: users/templates/users/user_detail.html:76
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
...@@ -392,7 +392,7 @@ msgid "Default" ...@@ -392,7 +392,7 @@ msgid "Default"
msgstr "默认" msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:13 #: assets/models/cluster.py:36 assets/models/label.py:13
#: users/models/user.py:330 #: users/models/user.py:343
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
...@@ -432,13 +432,13 @@ msgstr "默认资产组" ...@@ -432,13 +432,13 @@ msgstr "默认资产组"
#: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:281 #: terminal/templates/terminal/session_list.html:71 users/forms.py:281
#: users/models/user.py:30 users/models/user.py:318 #: users/models/user.py:31 users/models/user.py:331
#: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:339 #: users/templates/users/user_group_list.html:13 users/views/user.py:362
msgid "User" msgid "User"
msgstr "用户" msgstr "用户"
#: assets/models/label.py:18 assets/models/node.py:18 #: assets/models/label.py:18 assets/models/node.py:16
#: assets/templates/assets/label_list.html:15 common/models.py:27 #: assets/templates/assets/label_list.html:15 common/models.py:27
msgid "Value" msgid "Value"
msgstr "值" msgstr "值"
...@@ -447,7 +447,7 @@ msgstr "值" ...@@ -447,7 +447,7 @@ msgstr "值"
msgid "Category" msgid "Category"
msgstr "分类" msgstr "分类"
#: assets/models/node.py:14 #: assets/models/node.py:15
msgid "Key" msgid "Key"
msgstr "" msgstr ""
...@@ -630,16 +630,17 @@ msgstr "其它" ...@@ -630,16 +630,17 @@ msgstr "其它"
#: assets/templates/assets/domain_create_update.html:16 #: assets/templates/assets/domain_create_update.html:16
#: assets/templates/assets/gateway_create_update.html:58 #: assets/templates/assets/gateway_create_update.html:58
#: assets/templates/assets/label_create_update.html:18 #: assets/templates/assets/label_create_update.html:18
#: common/templates/common/basic_setting.html:58 #: common/templates/common/basic_setting.html:61
#: common/templates/common/email_setting.html:59 #: common/templates/common/email_setting.html:62
#: common/templates/common/ldap_setting.html:59 #: common/templates/common/ldap_setting.html:62
#: common/templates/common/terminal_setting.html:101 #: common/templates/common/security_setting.html:70
#: common/templates/common/terminal_setting.html:106
#: perms/templates/perms/asset_permission_create_update.html:69 #: perms/templates/perms/asset_permission_create_update.html:69
#: terminal/templates/terminal/terminal_update.html:47 #: terminal/templates/terminal/terminal_update.html:47
#: users/templates/users/_user.html:46 #: users/templates/users/_user.html:46
#: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_password_update.html:58 #: users/templates/users/user_password_update.html:70
#: users/templates/users/user_profile.html:181 #: users/templates/users/user_profile.html:188
#: users/templates/users/user_profile_update.html:63 #: users/templates/users/user_profile_update.html:63
#: users/templates/users/user_pubkey_update.html:70 #: users/templates/users/user_pubkey_update.html:70
#: users/templates/users/user_pubkey_update.html:76 #: users/templates/users/user_pubkey_update.html:76
...@@ -650,23 +651,24 @@ msgstr "重置" ...@@ -650,23 +651,24 @@ msgstr "重置"
#: assets/templates/assets/admin_user_create_update.html:46 #: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24 #: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:67 #: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_list.html:107 #: assets/templates/assets/asset_list.html:108
#: assets/templates/assets/asset_update.html:71 #: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/domain_create_update.html:17 #: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:59 #: assets/templates/assets/gateway_create_update.html:59
#: assets/templates/assets/label_create_update.html:19 #: assets/templates/assets/label_create_update.html:19
#: common/templates/common/basic_setting.html:59 #: common/templates/common/basic_setting.html:62
#: common/templates/common/email_setting.html:60 #: common/templates/common/email_setting.html:63
#: common/templates/common/ldap_setting.html:60 #: common/templates/common/ldap_setting.html:63
#: common/templates/common/terminal_setting.html:103 #: common/templates/common/security_setting.html:71
#: common/templates/common/terminal_setting.html:108
#: perms/templates/perms/asset_permission_create_update.html:70 #: perms/templates/perms/asset_permission_create_update.html:70
#: terminal/templates/terminal/session_list.html:124 #: terminal/templates/terminal/session_list.html:124
#: terminal/templates/terminal/terminal_update.html:48 #: terminal/templates/terminal/terminal_update.html:48
#: users/templates/users/_user.html:47 #: users/templates/users/_user.html:47
#: users/templates/users/forgot_password.html:44 #: users/templates/users/forgot_password.html:44
#: users/templates/users/user_bulk_update.html:24 #: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:44 #: users/templates/users/user_list.html:45
#: users/templates/users/user_password_update.html:59 #: users/templates/users/user_password_update.html:71
#: users/templates/users/user_profile_update.html:64 #: users/templates/users/user_profile_update.html:64
#: users/templates/users/user_pubkey_update.html:77 #: users/templates/users/user_pubkey_update.html:77
msgid "Submit" msgid "Submit"
...@@ -727,7 +729,7 @@ msgstr "测试" ...@@ -727,7 +729,7 @@ msgstr "测试"
#: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:85 #: assets/templates/assets/admin_user_list.html:85
#: assets/templates/assets/asset_detail.html:24 #: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_list.html:174 #: assets/templates/assets/asset_list.html:175
#: assets/templates/assets/domain_detail.html:24 #: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103 #: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:85 #: assets/templates/assets/domain_gateway_list.html:85
...@@ -742,16 +744,16 @@ msgstr "测试" ...@@ -742,16 +744,16 @@ msgstr "测试"
#: users/templates/users/user_detail.html:25 #: users/templates/users/user_detail.html:25
#: users/templates/users/user_group_detail.html:28 #: users/templates/users/user_group_detail.html:28
#: users/templates/users/user_group_list.html:43 #: users/templates/users/user_group_list.html:43
#: users/templates/users/user_list.html:76 #: users/templates/users/user_list.html:77
#: users/templates/users/user_profile.html:144 #: users/templates/users/user_profile.html:151
#: users/templates/users/user_profile.html:173 #: users/templates/users/user_profile.html:180
msgid "Update" msgid "Update"
msgstr "更新" msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28 #: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:86 #: assets/templates/assets/admin_user_list.html:86
#: assets/templates/assets/asset_detail.html:28 #: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_list.html:175 #: assets/templates/assets/asset_list.html:176
#: assets/templates/assets/domain_detail.html:28 #: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104 #: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:86 #: assets/templates/assets/domain_gateway_list.html:86
...@@ -766,8 +768,8 @@ msgstr "更新" ...@@ -766,8 +768,8 @@ msgstr "更新"
#: users/templates/users/user_detail.html:30 #: users/templates/users/user_detail.html:30
#: users/templates/users/user_group_detail.html:32 #: users/templates/users/user_group_detail.html:32
#: users/templates/users/user_group_list.html:45 #: users/templates/users/user_group_list.html:45
#: users/templates/users/user_list.html:80 #: users/templates/users/user_list.html:81
#: users/templates/users/user_list.html:84 #: users/templates/users/user_list.html:85
msgid "Delete" msgid "Delete"
msgstr "删除" msgstr "删除"
...@@ -782,17 +784,17 @@ msgstr "选择节点" ...@@ -782,17 +784,17 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200 #: assets/templates/assets/asset_detail.html:200
#: assets/templates/assets/asset_list.html:636 #: assets/templates/assets/asset_list.html:638
#: assets/templates/assets/system_user_detail.html:192 #: assets/templates/assets/system_user_detail.html:192
#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22 #: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108 #: terminal/templates/terminal/session_detail.html:108
#: users/templates/users/user_detail.html:362 #: users/templates/users/user_detail.html:366
#: users/templates/users/user_detail.html:387 #: users/templates/users/user_detail.html:391
#: users/templates/users/user_detail.html:410 #: users/templates/users/user_detail.html:414
#: users/templates/users/user_group_create_update.html:32 #: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:86 #: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:199 #: users/templates/users/user_list.html:200
#: users/templates/users/user_profile.html:215 #: users/templates/users/user_profile.html:222
msgid "Confirm" msgid "Confirm"
msgstr "确认" msgstr "确认"
...@@ -814,7 +816,7 @@ msgid "Ratio" ...@@ -814,7 +816,7 @@ msgid "Ratio"
msgstr "比例" msgstr "比例"
#: assets/templates/assets/admin_user_list.html:30 #: assets/templates/assets/admin_user_list.html:30
#: assets/templates/assets/asset_list.html:90 #: assets/templates/assets/asset_list.html:91
#: assets/templates/assets/domain_gateway_list.html:62 #: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:18 #: assets/templates/assets/domain_list.html:18
#: assets/templates/assets/label_list.html:17 #: assets/templates/assets/label_list.html:17
...@@ -825,7 +827,7 @@ msgstr "比例" ...@@ -825,7 +827,7 @@ msgstr "比例"
#: terminal/templates/terminal/session_list.html:80 #: terminal/templates/terminal/session_list.html:80
#: terminal/templates/terminal/terminal_list.html:36 #: terminal/templates/terminal/terminal_list.html:36
#: users/templates/users/user_group_list.html:15 #: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:28 #: users/templates/users/user_list.html:29
msgid "Action" msgid "Action"
msgstr "动作" msgstr "动作"
...@@ -842,20 +844,20 @@ msgid "Disk" ...@@ -842,20 +844,20 @@ msgid "Disk"
msgstr "硬盘" msgstr "硬盘"
#: assets/templates/assets/asset_detail.html:121 #: assets/templates/assets/asset_detail.html:121
#: users/templates/users/user_detail.html:111 #: users/templates/users/user_detail.html:115
#: users/templates/users/user_profile.html:97 #: users/templates/users/user_profile.html:104
msgid "Date joined" msgid "Date joined"
msgstr "创建日期" msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:137 #: assets/templates/assets/asset_detail.html:137
#: terminal/templates/terminal/session_detail.html:81 #: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:130 #: users/templates/users/user_detail.html:134
#: users/templates/users/user_profile.html:135 #: users/templates/users/user_profile.html:142
msgid "Quick modify" msgid "Quick modify"
msgstr "快速修改" msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:143 #: assets/templates/assets/asset_detail.html:143
#: assets/templates/assets/asset_list.html:88 #: assets/templates/assets/asset_list.html:89
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:35 #: assets/templates/assets/user_asset_list.html:47 perms/models.py:35
#: perms/models.py:79 #: perms/models.py:79
#: perms/templates/perms/asset_permission_create_update.html:47 #: perms/templates/perms/asset_permission_create_update.html:47
...@@ -863,10 +865,10 @@ msgstr "快速修改" ...@@ -863,10 +865,10 @@ msgstr "快速修改"
#: perms/templates/perms/asset_permission_list.html:59 #: perms/templates/perms/asset_permission_list.html:59
#: terminal/templates/terminal/terminal_list.html:34 #: terminal/templates/terminal/terminal_list.html:34
#: users/templates/users/_select_user_modal.html:18 #: users/templates/users/_select_user_modal.html:18
#: users/templates/users/user_detail.html:136 #: users/templates/users/user_detail.html:140
#: users/templates/users/user_granted_asset.html:46 #: users/templates/users/user_granted_asset.html:46
#: users/templates/users/user_group_granted_asset.html:46 #: users/templates/users/user_group_granted_asset.html:46
#: users/templates/users/user_list.html:27 #: users/templates/users/user_list.html:28
#: users/templates/users/user_profile.html:63 #: users/templates/users/user_profile.html:63
msgid "Active" msgid "Active"
msgstr "激活中" msgstr "激活中"
...@@ -880,124 +882,124 @@ msgid "Refresh" ...@@ -880,124 +882,124 @@ msgid "Refresh"
msgstr "刷新" msgstr "刷新"
#: assets/templates/assets/asset_detail.html:300 #: assets/templates/assets/asset_detail.html:300
#: users/templates/users/user_detail.html:282 #: users/templates/users/user_detail.html:286
#: users/templates/users/user_detail.html:309 #: users/templates/users/user_detail.html:313
msgid "Update successfully!" msgid "Update successfully!"
msgstr "更新成功" msgstr "更新成功"
#: assets/templates/assets/asset_list.html:62 assets/views/asset.py:97 #: assets/templates/assets/asset_list.html:63 assets/views/asset.py:97
msgid "Create asset" msgid "Create asset"
msgstr "创建资产" msgstr "创建资产"
#: assets/templates/assets/asset_list.html:66 #: assets/templates/assets/asset_list.html:67
#: users/templates/users/user_list.html:7 #: users/templates/users/user_list.html:7
msgid "Import" msgid "Import"
msgstr "导入" msgstr "导入"
#: assets/templates/assets/asset_list.html:69 #: assets/templates/assets/asset_list.html:70
#: users/templates/users/user_list.html:10 #: users/templates/users/user_list.html:10
msgid "Export" msgid "Export"
msgstr "导出" msgstr "导出"
#: assets/templates/assets/asset_list.html:87 #: assets/templates/assets/asset_list.html:88
msgid "Hardware" msgid "Hardware"
msgstr "硬件" msgstr "硬件"
#: assets/templates/assets/asset_list.html:99 #: assets/templates/assets/asset_list.html:100
#: users/templates/users/user_list.html:37 #: users/templates/users/user_list.html:38
msgid "Delete selected" msgid "Delete selected"
msgstr "批量删除" msgstr "批量删除"
#: assets/templates/assets/asset_list.html:100 #: assets/templates/assets/asset_list.html:101
#: users/templates/users/user_list.html:38 #: users/templates/users/user_list.html:39
msgid "Update selected" msgid "Update selected"
msgstr "批量更新" msgstr "批量更新"
#: assets/templates/assets/asset_list.html:101 #: assets/templates/assets/asset_list.html:102
msgid "Remove from this node" msgid "Remove from this node"
msgstr "从节点移除" msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:102 #: assets/templates/assets/asset_list.html:103
#: users/templates/users/user_list.html:39 #: users/templates/users/user_list.html:40
msgid "Deactive selected" msgid "Deactive selected"
msgstr "禁用所选" msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:103 #: assets/templates/assets/asset_list.html:104
#: users/templates/users/user_list.html:40 #: users/templates/users/user_list.html:41
msgid "Active selected" msgid "Active selected"
msgstr "激活所选" msgstr "激活所选"
#: assets/templates/assets/asset_list.html:120 #: assets/templates/assets/asset_list.html:121
msgid "Add node" msgid "Add node"
msgstr "新建节点" msgstr "新建节点"
#: assets/templates/assets/asset_list.html:121 #: assets/templates/assets/asset_list.html:122
msgid "Rename node" msgid "Rename node"
msgstr "重命名节点" msgstr "重命名节点"
#: assets/templates/assets/asset_list.html:122 #: assets/templates/assets/asset_list.html:123
msgid "Delete node" msgid "Delete node"
msgstr "删除节点" msgstr "删除节点"
#: assets/templates/assets/asset_list.html:124 #: assets/templates/assets/asset_list.html:125
msgid "Add assets to node" msgid "Add assets to node"
msgstr "添加资产到节点" msgstr "添加资产到节点"
#: assets/templates/assets/asset_list.html:125 #: assets/templates/assets/asset_list.html:126
msgid "Move assets to node" msgid "Move assets to node"
msgstr "移动资产到节点" msgstr "移动资产到节点"
#: assets/templates/assets/asset_list.html:127 #: assets/templates/assets/asset_list.html:128
msgid "Refresh node hardware info" msgid "Refresh node hardware info"
msgstr "更新节点资产硬件信息" msgstr "更新节点资产硬件信息"
#: assets/templates/assets/asset_list.html:128 #: assets/templates/assets/asset_list.html:129
msgid "Test node connective" msgid "Test node connective"
msgstr "测试节点资产可连接性" msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:130 #: assets/templates/assets/asset_list.html:131
msgid "Display only current node assets" msgid "Display only current node assets"
msgstr "仅显示当前节点资产" msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:131 #: assets/templates/assets/asset_list.html:132
msgid "Displays all child node assets" msgid "Displays all child node assets"
msgstr "显示所有子节点资产" msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:217 #: assets/templates/assets/asset_list.html:218
msgid "Create node failed" msgid "Create node failed"
msgstr "创建节点失败" msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:229 #: assets/templates/assets/asset_list.html:230
msgid "Have child node, cancel" msgid "Have child node, cancel"
msgstr "存在子节点,不能删除" msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:231 #: assets/templates/assets/asset_list.html:232
msgid "Have assets, cancel" msgid "Have assets, cancel"
msgstr "存在资产,不能删除" msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:631 #: assets/templates/assets/asset_list.html:633
#: assets/templates/assets/system_user_list.html:133 #: assets/templates/assets/system_user_list.html:133
#: users/templates/users/user_detail.html:357 #: users/templates/users/user_detail.html:361
#: users/templates/users/user_detail.html:382 #: users/templates/users/user_detail.html:386
#: users/templates/users/user_group_list.html:81 #: 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?" msgid "Are you sure?"
msgstr "你确认吗?" msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:632 #: assets/templates/assets/asset_list.html:634
msgid "This will delete the selected assets !!!" msgid "This will delete the selected assets !!!"
msgstr "删除选择资产" msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:640 #: assets/templates/assets/asset_list.html:642
msgid "Asset Deleted." msgid "Asset Deleted."
msgstr "已被删除" msgstr "已被删除"
#: assets/templates/assets/asset_list.html:641 #: assets/templates/assets/asset_list.html:643
#: assets/templates/assets/asset_list.html:646 #: assets/templates/assets/asset_list.html:648
msgid "Asset Delete" msgid "Asset Delete"
msgstr "删除" msgstr "删除"
#: assets/templates/assets/asset_list.html:645 #: assets/templates/assets/asset_list.html:647
msgid "Asset Deleting failed." msgid "Asset Deleting failed."
msgstr "删除失败" msgstr "删除失败"
...@@ -1033,8 +1035,8 @@ msgstr "创建网关" ...@@ -1033,8 +1035,8 @@ msgstr "创建网关"
#: assets/templates/assets/domain_gateway_list.html:87 #: assets/templates/assets/domain_gateway_list.html:87
#: assets/templates/assets/domain_gateway_list.html:89 #: assets/templates/assets/domain_gateway_list.html:89
#: common/templates/common/email_setting.html:58 #: common/templates/common/email_setting.html:61
#: common/templates/common/ldap_setting.html:58 #: common/templates/common/ldap_setting.html:61
msgid "Test connection" msgid "Test connection"
msgstr "测试连接" msgstr "测试连接"
...@@ -1237,11 +1239,11 @@ msgstr "FTP日志" ...@@ -1237,11 +1239,11 @@ msgstr "FTP日志"
msgid "Test mail sent to {}, please check" msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查" msgstr "邮件已经发送{}, 请检查"
#: common/api.py:52 #: common/api.py:42
msgid "Test ldap success" msgid "Test ldap success"
msgstr "连接LDAP成功" msgstr "连接LDAP成功"
#: common/api.py:90 #: common/api.py:80
msgid "Match {} s users" msgid "Match {} s users"
msgstr "匹配 {} 个用户" msgstr "匹配 {} 个用户"
...@@ -1376,7 +1378,7 @@ msgstr "密码认证" ...@@ -1376,7 +1378,7 @@ msgstr "密码认证"
msgid "Public key auth" msgid "Public key auth"
msgstr "密钥认证" msgstr "密钥认证"
#: common/forms.py:159 common/templates/common/terminal_setting.html:63 #: common/forms.py:159 common/templates/common/terminal_setting.html:68
#: terminal/forms.py:30 terminal/models.py:20 #: terminal/forms.py:30 terminal/models.py:20
msgid "Command storage" msgid "Command storage"
msgstr "命令存储" msgstr "命令存储"
...@@ -1387,7 +1389,7 @@ msgid "" ...@@ -1387,7 +1389,7 @@ msgid ""
"other storage and some terminal using" "other storage and some terminal using"
msgstr "设置终端命令存储,default是默认用的存储方式" msgstr "设置终端命令存储,default是默认用的存储方式"
#: common/forms.py:165 common/templates/common/terminal_setting.html:81 #: common/forms.py:165 common/templates/common/terminal_setting.html:86
#: terminal/forms.py:35 terminal/models.py:21 #: terminal/forms.py:35 terminal/models.py:21
msgid "Replay storage" msgid "Replay storage"
msgstr "录像存储" msgstr "录像存储"
...@@ -1398,6 +1400,60 @@ msgid "" ...@@ -1398,6 +1400,60 @@ msgid ""
"other storage and some terminal using" "other storage and some terminal using"
msgstr "设置终端录像存储,default是默认用的存储方式" msgstr "设置终端录像存储,default是默认用的存储方式"
#: common/forms.py:176
msgid "MFA Secondary certification"
msgstr "MFA 二次认证"
#: common/forms.py:178
msgid ""
"After opening, the user login must use MFA secondary authentication (valid "
"for all users, including administrators)"
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
#: common/forms.py:184
msgid "Password minimum length"
msgstr "密码最小长度 "
#: common/forms.py: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 #: common/mixins.py:29
msgid "is discard" msgid "is discard"
msgstr "" msgstr ""
...@@ -1413,14 +1469,16 @@ msgstr "启用" ...@@ -1413,14 +1469,16 @@ msgstr "启用"
#: common/templates/common/basic_setting.html:15 #: common/templates/common/basic_setting.html:15
#: common/templates/common/email_setting.html:15 #: common/templates/common/email_setting.html:15
#: common/templates/common/ldap_setting.html:15 #: common/templates/common/ldap_setting.html:15
#: common/templates/common/security_setting.html:15
#: common/templates/common/terminal_setting.html:16 #: common/templates/common/terminal_setting.html:16
#: common/templates/common/terminal_setting.html:42 common/views.py:22 #: common/templates/common/terminal_setting.html:46 common/views.py:22
msgid "Basic setting" msgid "Basic setting"
msgstr "基本设置" msgstr "基本设置"
#: common/templates/common/basic_setting.html:18 #: common/templates/common/basic_setting.html:18
#: common/templates/common/email_setting.html:18 #: common/templates/common/email_setting.html:18
#: common/templates/common/ldap_setting.html:18 #: common/templates/common/ldap_setting.html:18
#: common/templates/common/security_setting.html:18
#: common/templates/common/terminal_setting.html:20 common/views.py:48 #: common/templates/common/terminal_setting.html:20 common/views.py:48
msgid "Email setting" msgid "Email setting"
msgstr "邮件设置" msgstr "邮件设置"
...@@ -1428,6 +1486,7 @@ msgstr "邮件设置" ...@@ -1428,6 +1486,7 @@ msgstr "邮件设置"
#: common/templates/common/basic_setting.html:21 #: common/templates/common/basic_setting.html:21
#: common/templates/common/email_setting.html:21 #: common/templates/common/email_setting.html:21
#: common/templates/common/ldap_setting.html:21 #: common/templates/common/ldap_setting.html:21
#: common/templates/common/security_setting.html:21
#: common/templates/common/terminal_setting.html:24 common/views.py:74 #: common/templates/common/terminal_setting.html:24 common/views.py:74
msgid "LDAP setting" msgid "LDAP setting"
msgstr "LDAP设置" msgstr "LDAP设置"
...@@ -1435,12 +1494,29 @@ msgstr "LDAP设置" ...@@ -1435,12 +1494,29 @@ msgstr "LDAP设置"
#: common/templates/common/basic_setting.html:24 #: common/templates/common/basic_setting.html:24
#: common/templates/common/email_setting.html:24 #: common/templates/common/email_setting.html:24
#: common/templates/common/ldap_setting.html:24 #: common/templates/common/ldap_setting.html:24
#: common/templates/common/security_setting.html:24
#: common/templates/common/terminal_setting.html:28 common/views.py:104 #: common/templates/common/terminal_setting.html:28 common/views.py:104
msgid "Terminal setting" msgid "Terminal setting"
msgstr "终端设置" msgstr "终端设置"
#: common/templates/common/terminal_setting.html:68 #: common/templates/common/basic_setting.html:27
#: common/templates/common/terminal_setting.html:86 #: common/templates/common/email_setting.html:27
#: common/templates/common/ldap_setting.html:27
#: common/templates/common/security_setting.html:27
#: common/templates/common/terminal_setting.html:31 common/views.py:132
msgid "Security setting"
msgstr "安全设置"
#: common/templates/common/security_setting.html:42
msgid "MFA setting"
msgstr "MFA 设置"
#: common/templates/common/security_setting.html:46
msgid "Password check rule"
msgstr "密码校验规则"
#: common/templates/common/terminal_setting.html:73
#: common/templates/common/terminal_setting.html:91
#: users/templates/users/login_log_list.html:50 #: users/templates/users/login_log_list.html:50
msgid "Type" msgid "Type"
msgstr "类型" msgstr "类型"
...@@ -1450,11 +1526,12 @@ msgid "Special char not allowed" ...@@ -1450,11 +1526,12 @@ msgid "Special char not allowed"
msgstr "不能包含特殊字符" msgstr "不能包含特殊字符"
#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:103 #: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:103
#: templates/_nav.html:81 #: common/views.py:131 templates/_nav.html:81
msgid "Settings" msgid "Settings"
msgstr "系统设置" msgstr "系统设置"
#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:116 #: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:116
#: common/views.py:142
msgid "Update setting successfully, please restart program" msgid "Update setting successfully, please restart program"
msgstr "更新设置成功, 请手动重启程序" msgstr "更新设置成功, 请手动重启程序"
...@@ -1736,9 +1813,9 @@ msgstr "选择用户" ...@@ -1736,9 +1813,9 @@ msgstr "选择用户"
#: perms/forms.py:34 perms/models.py:31 perms/models.py:77 #: 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:55
#: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14 #: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14
#: users/models/group.py:25 users/models/user.py:48 #: users/models/group.py:23 users/models/user.py:55
#: users/templates/users/_select_user_modal.html:16 #: 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 #: users/templates/users/user_list.html:26
msgid "User group" msgid "User group"
msgstr "用户组" msgstr "用户组"
...@@ -1753,8 +1830,8 @@ msgstr "" ...@@ -1753,8 +1830,8 @@ msgstr ""
#: perms/models.py:37 perms/models.py:80 #: perms/models.py:37 perms/models.py:80
#: perms/templates/perms/asset_permission_detail.html:90 #: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:80 users/templates/users/user_detail.html:103 #: users/models/user.py:87 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:105 #: users/templates/users/user_profile.html:112
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
...@@ -1791,7 +1868,7 @@ msgid "Add node to this permission" ...@@ -1791,7 +1868,7 @@ msgid "Add node to this permission"
msgstr "添加节点" msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:125 #: perms/templates/perms/asset_permission_asset.html:125
#: users/templates/users/user_detail.html:205 #: users/templates/users/user_detail.html:209
msgid "Join" msgid "Join"
msgstr "加入" msgstr "加入"
...@@ -1884,11 +1961,11 @@ msgstr "文档" ...@@ -1884,11 +1961,11 @@ msgstr "文档"
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:121 #: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:121
#: users/templates/users/_user.html:39 #: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39 #: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:37 #: users/templates/users/user_password_update.html:39
#: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57 #: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:322 #: users/templates/users/user_pubkey_update.html:37 users/views/user.py:344
msgid "Profile" msgid "Profile"
msgstr "个人信息" msgstr "个人信息"
...@@ -1945,13 +2022,13 @@ msgstr "关闭" ...@@ -1945,13 +2022,13 @@ msgstr "关闭"
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95 #: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
#: users/views/login.py:263 users/views/login.py:321 users/views/user.py:64 #: users/views/login.py:277 users/views/login.py:335 users/views/user.py:66
#: users/views/user.py:79 users/views/user.py:99 users/views/user.py:155 #: users/views/user.py:81 users/views/user.py:103 users/views/user.py:174
#: users/views/user.py:310 users/views/user.py:357 users/views/user.py:379 #: users/views/user.py:329 users/views/user.py:381 users/views/user.py:416
msgid "Users" msgid "Users"
msgstr "用户管理" msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:65 #: templates/_nav.html:13 users/views/user.py:67
msgid "User list" msgid "User list"
msgstr "用户列表" msgstr "用户列表"
...@@ -2261,7 +2338,7 @@ msgstr "" ...@@ -2261,7 +2338,7 @@ msgstr ""
msgid "MFA code" msgid "MFA code"
msgstr "MFA 验证码" 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/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87 #: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:25 #: users/templates/users/user_list.html:25
...@@ -2281,7 +2358,7 @@ msgstr "" ...@@ -2281,7 +2358,7 @@ msgstr ""
msgid "Paste user id_rsa.pub here." msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里" 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" msgid "Join user groups"
msgstr "添加到用户组" msgstr "添加到用户组"
...@@ -2289,7 +2366,7 @@ msgstr "添加到用户组" ...@@ -2289,7 +2366,7 @@ msgstr "添加到用户组"
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms.py: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" msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法" msgstr "ssh密钥不合法"
...@@ -2306,7 +2383,7 @@ msgstr "" ...@@ -2306,7 +2383,7 @@ msgstr ""
msgid "* Enable MFA authentication to make the account more secure." msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全." 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 #: users/templates/users/first_login.html:45
msgid "MFA" msgid "MFA"
msgstr "MFA" msgstr "MFA"
...@@ -2354,9 +2431,9 @@ msgstr "自动配置并下载SSH密钥" ...@@ -2354,9 +2431,9 @@ msgstr "自动配置并下载SSH密钥"
msgid "Paste your id_rsa.pub here." msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里" 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/first_login.html:42
#: users/templates/users/user_password_update.html:43 #: users/templates/users/user_password_update.html:45
#: users/templates/users/user_profile.html:68 #: users/templates/users/user_profile.html:68
#: users/templates/users/user_profile_update.html:43 #: users/templates/users/user_profile_update.html:43
#: users/templates/users/user_pubkey_update.html:43 #: users/templates/users/user_pubkey_update.html:43
...@@ -2387,43 +2464,49 @@ msgstr "Agent" ...@@ -2387,43 +2464,49 @@ msgstr "Agent"
msgid "Date login" msgid "Date login"
msgstr "登录日期" msgstr "登录日期"
#: users/models/user.py:29 users/models/user.py:326 #: users/models/user.py:30 users/models/user.py:339
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
#: users/models/user.py:31 #: users/models/user.py:32
msgid "Application" msgid "Application"
msgstr "应用程序" msgstr "应用程序"
#: users/models/user.py:34 users/templates/users/user_profile.html:92 #: users/models/user.py:35 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:156 #: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:159 #: users/templates/users/user_profile.html:166
msgid "Disable" msgid "Disable"
msgstr "禁用" msgstr "禁用"
#: users/models/user.py:35 users/templates/users/user_profile.html:90 #: users/models/user.py:36 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:170
msgid "Enable" msgid "Enable"
msgstr "启用" 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" msgid "Force enable"
msgstr "强制启用" 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 #: users/templates/users/user_profile.html:59
msgid "Email" msgid "Email"
msgstr "邮件" msgstr "邮件"
#: users/models/user.py:55 #: users/models/user.py:62
msgid "Avatar" msgid "Avatar"
msgstr "头像" 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" msgid "Wechat"
msgstr "微信" 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" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
...@@ -2539,22 +2622,34 @@ msgstr "6位数字" ...@@ -2539,22 +2622,34 @@ msgstr "6位数字"
msgid "Can't provide security? Please contact the administrator!" msgid "Can't provide security? Please contact the administrator!"
msgstr "如果不能提供MFA验证码,请联系管理员!" msgstr "如果不能提供MFA验证码,请联系管理员!"
#: users/templates/users/reset_password.html:45 #: users/templates/users/reset_password.html:46
#: users/templates/users/user_detail.html:348 users/utils.py:76 #: users/templates/users/user_detail.html:352 users/utils.py:80
msgid "Reset password" msgid "Reset password"
msgstr "重置密码" msgstr "重置密码"
#: users/templates/users/reset_password.html:55 #: users/templates/users/reset_password.html:59
#: users/templates/users/user_password_update.html:60
#: users/templates/users/user_update.html:12
msgid "Your password must satisfy"
msgstr "您的密码必须满足:"
#: users/templates/users/reset_password.html:60
#: users/templates/users/user_password_update.html:61
#: users/templates/users/user_update.html:13
msgid "Password strength"
msgstr "密码强度:"
#: users/templates/users/reset_password.html:66
msgid "Password again" msgid "Password again"
msgstr "再次输入密码" msgstr "再次输入密码"
#: users/templates/users/reset_password.html:57 #: users/templates/users/reset_password.html:68
#: users/templates/users/user_profile.html:20 #: users/templates/users/user_profile.html:20
msgid "Setting" msgid "Setting"
msgstr "设置" msgstr "设置"
#: users/templates/users/user_create.html:4 #: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:16 users/views/user.py:79 #: users/templates/users/user_list.html:16 users/views/user.py:81
msgid "Create user" msgid "Create user"
msgstr "创建用户" msgstr "创建用户"
...@@ -2563,7 +2658,7 @@ msgid "Reset link will be generated and sent to the user. " ...@@ -2563,7 +2658,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户" msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19 #: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:156 #: users/templates/users/user_granted_asset.html:18 users/views/user.py:175
msgid "User detail" msgid "User detail"
msgstr "用户详情" msgstr "用户详情"
...@@ -2582,63 +2677,63 @@ msgstr "强制启用" ...@@ -2582,63 +2677,63 @@ msgstr "强制启用"
msgid "Disabled" msgid "Disabled"
msgstr "禁用" msgstr "禁用"
#: users/templates/users/user_detail.html:115 #: users/templates/users/user_detail.html:119
#: users/templates/users/user_profile.html:101 #: users/templates/users/user_profile.html:108
msgid "Last login" msgid "Last login"
msgstr "最后登录" msgstr "最后登录"
#: users/templates/users/user_detail.html:151 #: users/templates/users/user_detail.html:155
msgid "Force enabled MFA" msgid "Force enabled MFA"
msgstr "强制启用MFA" msgstr "强制启用MFA"
#: users/templates/users/user_detail.html:166 #: users/templates/users/user_detail.html:170
msgid "Send reset password mail" msgid "Send reset password mail"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/templates/users/user_detail.html:169 #: users/templates/users/user_detail.html:173
#: users/templates/users/user_detail.html:177 #: users/templates/users/user_detail.html:181
msgid "Send" msgid "Send"
msgstr "发送" msgstr "发送"
#: users/templates/users/user_detail.html:174 #: users/templates/users/user_detail.html:178
msgid "Send reset ssh key mail" msgid "Send reset ssh key mail"
msgstr "发送重置密钥邮件" msgstr "发送重置密钥邮件"
#: users/templates/users/user_detail.html:291 #: users/templates/users/user_detail.html:295
msgid "Goto profile page enable MFA" msgid "Goto profile page enable MFA"
msgstr "请去个人信息页面启用自己的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." msgid "An e-mail has been sent to the user`s mailbox."
msgstr "已发送邮件到用户邮箱" 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" msgid "This will reset the user password and send a reset mail"
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱" msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
#: users/templates/users/user_detail.html:372 #: users/templates/users/user_detail.html:376
msgid "" msgid ""
"The reset-ssh-public-key E-mail has been sent successfully. Please inform " "The reset-ssh-public-key E-mail has been sent successfully. Please inform "
"the user to update his new ssh public key." "the user to update his new ssh public key."
msgstr "重设密钥邮件将会发送到用户邮箱" msgstr "重设密钥邮件将会发送到用户邮箱"
#: users/templates/users/user_detail.html:373 #: users/templates/users/user_detail.html:377
msgid "Reset SSH public key" msgid "Reset SSH public key"
msgstr "重置SSH密钥" 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" msgid "This will reset the user public key and send a reset mail"
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱" msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
#: users/templates/users/user_detail.html:400 #: users/templates/users/user_detail.html:404
#: users/templates/users/user_profile.html:204 #: users/templates/users/user_profile.html:211
msgid "Successfully updated the SSH public key." msgid "Successfully updated the SSH public key."
msgstr "更新ssh密钥成功" msgstr "更新ssh密钥成功"
#: users/templates/users/user_detail.html:401
#: users/templates/users/user_detail.html:405 #: users/templates/users/user_detail.html:405
#: users/templates/users/user_profile.html:205 #: users/templates/users/user_detail.html:409
#: users/templates/users/user_profile.html:210 #: users/templates/users/user_profile.html:212
#: users/templates/users/user_profile.html:217
msgid "User SSH public key update" msgid "User SSH public key update"
msgstr "ssh密钥" msgstr "ssh密钥"
...@@ -2677,45 +2772,49 @@ msgstr "用户组删除" ...@@ -2677,45 +2772,49 @@ msgstr "用户组删除"
msgid "UserGroup Deleting failed." msgid "UserGroup Deleting failed."
msgstr "用户组删除失败" msgstr "用户组删除失败"
#: users/templates/users/user_list.html:195 #: users/templates/users/user_list.html:196
msgid "This will delete the selected users !!!" msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!" msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:203 #: users/templates/users/user_list.html:204
msgid "User Deleted." msgid "User Deleted."
msgstr "已被删除" msgstr "已被删除"
#: users/templates/users/user_list.html:204 #: users/templates/users/user_list.html:205
#: users/templates/users/user_list.html:209 #: users/templates/users/user_list.html:210
msgid "User Delete" msgid "User Delete"
msgstr "删除" msgstr "删除"
#: users/templates/users/user_list.html:208 #: users/templates/users/user_list.html:209
msgid "User Deleting failed." msgid "User Deleting failed."
msgstr "用户删除失败" msgstr "用户删除失败"
#: users/templates/users/user_profile.html:109 users/views/user.py:185 #: users/templates/users/user_profile.html:95
#: users/views/user.py:239 msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录"
#: users/templates/users/user_profile.html:116 users/views/user.py:204
#: users/views/user.py:258
msgid "User groups" msgid "User groups"
msgstr "用户组" msgstr "用户组"
#: users/templates/users/user_profile.html:141 #: users/templates/users/user_profile.html:148
msgid "Update password" msgid "Update password"
msgstr "更改密码" msgstr "更改密码"
#: users/templates/users/user_profile.html:149 #: users/templates/users/user_profile.html:156
msgid "Update MFA settings" msgid "Update MFA settings"
msgstr "更改MFA设置" msgstr "更改MFA设置"
#: users/templates/users/user_profile.html:170 #: users/templates/users/user_profile.html:177
msgid "Update SSH public key" msgid "Update SSH public key"
msgstr "更改SSH密钥" msgstr "更改SSH密钥"
#: users/templates/users/user_profile.html:178 #: users/templates/users/user_profile.html:185
msgid "Reset public key and download" msgid "Reset public key and download"
msgstr "重置并下载SSH密钥" msgstr "重置并下载SSH密钥"
#: users/templates/users/user_profile.html:208 #: users/templates/users/user_profile.html:215
msgid "Failed to update SSH public key." msgid "Failed to update SSH public key."
msgstr "更新密钥失败" msgstr "更新密钥失败"
...@@ -2735,15 +2834,21 @@ msgstr "更新密钥" ...@@ -2735,15 +2834,21 @@ msgstr "更新密钥"
msgid "Or reset by server" msgid "Or reset by server"
msgstr "或者重置并下载密钥" msgstr "或者重置并下载密钥"
#: users/templates/users/user_update.html:4 users/views/user.py:99 #: users/templates/users/user_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" msgid "Update user"
msgstr "更新用户" msgstr "更新用户"
#: users/utils.py:37 #: users/utils.py:41
msgid "Create account successfully" msgid "Create account successfully"
msgstr "创建账户成功" msgstr "创建账户成功"
#: users/utils.py:39 #: users/utils.py:43
#, fuzzy, python-format #, fuzzy, python-format
msgid "" msgid ""
"\n" "\n"
...@@ -2788,7 +2893,7 @@ msgstr "" ...@@ -2788,7 +2893,7 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:78 #: users/utils.py:82
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
...@@ -2832,11 +2937,11 @@ msgstr "" ...@@ -2832,11 +2937,11 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:109 #: users/utils.py:113
msgid "SSH Key Reset" msgid "SSH Key Reset"
msgstr "重置ssh密钥" msgstr "重置ssh密钥"
#: users/utils.py:111 #: users/utils.py:115
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
...@@ -2861,18 +2966,22 @@ msgstr "" ...@@ -2861,18 +2966,22 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:144 #: users/utils.py:148
msgid "User not exist" msgid "User not exist"
msgstr "用户不存在" msgstr "用户不存在"
#: users/utils.py:146 #: users/utils.py:150
msgid "Disabled or expired" msgid "Disabled or expired"
msgstr "禁用或失效" msgstr "禁用或失效"
#: users/utils.py:159 #: users/utils.py:163
msgid "Password or SSH public key invalid" msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法" msgstr "密码或密钥不合法"
#: users/utils.py:290 users/utils.py:300
msgid "Bit"
msgstr " 位"
#: users/views/group.py:29 #: users/views/group.py:29
msgid "User group list" msgid "User group list"
msgstr "用户组列表" msgstr "用户组列表"
...@@ -2885,99 +2994,103 @@ msgstr "更新用户组" ...@@ -2885,99 +2994,103 @@ msgstr "更新用户组"
msgid "User group granted asset" msgid "User group granted asset"
msgstr "用户组授权资产" msgstr "用户组授权资产"
#: users/views/login.py:59 #: users/views/login.py:62
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:125 users/views/user.py:464 users/views/user.py:489 #: users/views/login.py:128 users/views/user.py:501 users/views/user.py:526
msgid "MFA code invalid" msgid "MFA code invalid"
msgstr "MFA码认证失败" msgstr "MFA码认证失败"
#: users/views/login.py:151 #: users/views/login.py:154
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
#: users/views/login.py:152 #: users/views/login.py:155
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:168 #: users/views/login.py:171
msgid "Email address invalid, please input again" msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入" msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:181 #: users/views/login.py:184
msgid "Send reset password message" msgid "Send reset password message"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/views/login.py:182 #: users/views/login.py:185
msgid "Send reset password mail success, login your mail box and follow it " msgid "Send reset password mail success, login your mail box and follow it "
msgstr "" msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:195 #: users/views/login.py:198
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
#: users/views/login.py:196 #: users/views/login.py:199
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面" msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:213 users/views/login.py:226 #: users/views/login.py:220 users/views/login.py:233
msgid "Token invalid or expired" msgid "Token invalid or expired"
msgstr "Token错误或失效" msgstr "Token错误或失效"
#: users/views/login.py:222 #: users/views/login.py:229
msgid "Password not same" msgid "Password not same"
msgstr "密码不一致" 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" msgid "First login"
msgstr "首次登陆" msgstr "首次登陆"
#: users/views/login.py:322 #: users/views/login.py:336
msgid "Login log list" msgid "Login log list"
msgstr "登录日志" msgstr "登录日志"
#: users/views/user.py:109 #: users/views/user.py:128
msgid "Bulk update user success" msgid "Bulk update user success"
msgstr "批量更新用户成功" msgstr "批量更新用户成功"
#: users/views/user.py:214 #: users/views/user.py:233
msgid "Invalid file." msgid "Invalid file."
msgstr "文件不合法" msgstr "文件不合法"
#: users/views/user.py:311 #: users/views/user.py:330
msgid "User granted assets" msgid "User granted assets"
msgstr "用户授权资产" msgstr "用户授权资产"
#: users/views/user.py:340 #: users/views/user.py:363
msgid "Profile setting" msgid "Profile setting"
msgstr "个人信息设置" msgstr "个人信息设置"
#: users/views/user.py:358 #: users/views/user.py:382
msgid "Password update" msgid "Password update"
msgstr "密码更新" msgstr "密码更新"
#: users/views/user.py:380 #: users/views/user.py:417
msgid "Public key update" msgid "Public key update"
msgstr "密钥更新" msgstr "密钥更新"
#: users/views/user.py:421 #: users/views/user.py:458
msgid "Password invalid" msgid "Password invalid"
msgstr "用户名或密码无效" msgstr "用户名或密码无效"
#: users/views/user.py:515 #: users/views/user.py:552
msgid "MFA enable success" msgid "MFA enable success"
msgstr "MFA 绑定成功" msgstr "MFA 绑定成功"
#: users/views/user.py:516 #: users/views/user.py:553
msgid "MFA enable success, return login page" msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面" msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:518 #: users/views/user.py:555
msgid "MFA disable success" msgid "MFA disable success"
msgstr "MFA 解绑成功" msgstr "MFA 解绑成功"
#: users/views/user.py:519 #: users/views/user.py:556
msgid "MFA disable success, return login page" msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面" msgstr "MFA 解绑成功,返回登录页面"
...@@ -401,6 +401,9 @@ TERMINAL_REPLAY_STORAGE = { ...@@ -401,6 +401,9 @@ TERMINAL_REPLAY_STORAGE = {
}, },
} }
DEFAULT_PASSWORD_MIN_LENGTH = 6
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = { BOOTSTRAP3 = {
'horizontal_label_class': 'col-md-2', 'horizontal_label_class': 'col-md-2',
......
...@@ -86,6 +86,7 @@ class JMSInventory(BaseInventory): ...@@ -86,6 +86,7 @@ class JMSInventory(BaseInventory):
gateway = asset.domain.random_gateway() gateway = asset.domain.random_gateway()
proxy_command_list = [ proxy_command_list = [
"ssh", "-p", str(gateway.port), "ssh", "-p", str(gateway.port),
"-o", "StrictHostKeyChecking=no",
"{}@{}".format(gateway.username, gateway.ip), "{}@{}".format(gateway.username, gateway.ip),
"-W", "%h:%p", "-q", "-W", "%h:%p", "-q",
] ]
......
...@@ -11,10 +11,48 @@ from .hands import Node ...@@ -11,10 +11,48 @@ from .hands import Node
logger = get_logger(__file__) logger = get_logger(__file__)
class Tree:
def __init__(self):
self.__all_nodes = list(Node.objects.all().prefetch_related('assets'))
self.__node_asset_map = defaultdict(set)
self.nodes = defaultdict(dict)
self.root = Node.root()
self.init_node_asset_map()
def init_node_asset_map(self):
for node in self.__all_nodes:
assets = node.get_assets().values_list('id', flat=True)
for asset in assets:
self.__node_asset_map[str(asset)].add(node)
def add_asset(self, asset, system_users):
nodes = self.__node_asset_map.get(str(asset.id), [])
self.add_nodes(nodes)
for node in nodes:
self.nodes[node][asset].update(system_users)
def add_node(self, node):
if node in self.nodes:
return
else:
self.nodes[node] = defaultdict(set)
if node.key == self.root.key:
return
parent_key = ':'.join(node.key.split(':')[:-1])
for n in self.__all_nodes:
if n.key == parent_key:
self.add_node(n)
break
def add_nodes(self, nodes):
for node in nodes:
self.add_node(node)
def get_user_permissions(user, include_group=True): def get_user_permissions(user, include_group=True):
if include_group: if include_group:
groups = user.groups.all() groups = user.groups.all()
arg = Q(users=user) | Q(user_groups=groups) arg = Q(users=user) | Q(user_groups__in=groups)
else: else:
arg = Q(users=user) arg = Q(users=user)
return AssetPermission.objects.all().valid().filter(arg) return AssetPermission.objects.all().valid().filter(arg)
...@@ -29,7 +67,7 @@ def get_user_group_permissions(user_group): ...@@ -29,7 +67,7 @@ def get_user_group_permissions(user_group):
def get_asset_permissions(asset, include_node=True): def get_asset_permissions(asset, include_node=True):
if include_node: if include_node:
nodes = asset.get_all_nodes(flat=True) nodes = asset.get_all_nodes(flat=True)
arg = Q(assets=asset) | Q(nodes=nodes) arg = Q(assets=asset) | Q(nodes__in=nodes)
else: else:
arg = Q(assets=asset) arg = Q(assets=asset)
return AssetPermission.objects.all().valid().filter(arg) return AssetPermission.objects.all().valid().filter(arg)
...@@ -57,6 +95,7 @@ class AssetPermissionUtil: ...@@ -57,6 +95,7 @@ class AssetPermissionUtil:
def __init__(self, obj): def __init__(self, obj):
self.object = obj self.object = obj
self._permissions = None self._permissions = None
self._assets = None
@property @property
def permissions(self): def permissions(self):
...@@ -93,6 +132,8 @@ class AssetPermissionUtil: ...@@ -93,6 +132,8 @@ class AssetPermissionUtil:
return assets return assets
def get_assets(self): def get_assets(self):
if self._assets:
return self._assets
assets = self.get_assets_direct() assets = self.get_assets_direct()
nodes = self.get_nodes_direct() nodes = self.get_nodes_direct()
for node, system_users in nodes.items(): for node, system_users in nodes.items():
...@@ -101,7 +142,8 @@ class AssetPermissionUtil: ...@@ -101,7 +142,8 @@ class AssetPermissionUtil:
if isinstance(asset, Node): if isinstance(asset, Node):
print(_assets) print(_assets)
assets[asset].update(system_users) assets[asset].update(system_users)
return assets self._assets = assets
return self._assets
def get_nodes_with_assets(self): def get_nodes_with_assets(self):
""" """
...@@ -110,14 +152,9 @@ class AssetPermissionUtil: ...@@ -110,14 +152,9 @@ class AssetPermissionUtil:
:return: :return:
""" """
assets = self.get_assets() assets = self.get_assets()
nodes = defaultdict(dict) tree = Tree()
for asset, system_users in assets.items(): for asset, system_users in assets.items():
_nodes = asset.nodes.all() tree.add_asset(asset, system_users)
for node in _nodes: return tree.nodes
if asset in nodes[node]:
nodes[node][asset].update(system_users)
else:
nodes[node][asset] = system_users
return nodes
...@@ -609,3 +609,91 @@ function setUrlParam(url, name, value) { ...@@ -609,3 +609,91 @@ function setUrlParam(url, name, value) {
} }
return url return url
} }
// 校验密码-改变规则颜色
function checkPasswordRules(password, minLength) {
if (wordMinLength(password, minLength)) {
$('#rule_SECURITY_PASSWORD_MIN_LENGTH').css('color', 'green')
}
else {
$('#rule_SECURITY_PASSWORD_MIN_LENGTH').css('color', '#908a8a')
}
if (wordUpperCase(password)) {
$('#rule_SECURITY_PASSWORD_UPPER_CASE').css('color', 'green');
}
else {
$('#rule_SECURITY_PASSWORD_UPPER_CASE').css('color', '#908a8a')
}
if (wordLowerCase(password)) {
$('#rule_SECURITY_PASSWORD_LOWER_CASE').css('color', 'green')
}
else {
$('#rule_SECURITY_PASSWORD_LOWER_CASE').css('color', '#908a8a')
}
if (wordNumber(password)) {
$('#rule_SECURITY_PASSWORD_NUMBER').css('color', 'green')
}
else {
$('#rule_SECURITY_PASSWORD_NUMBER').css('color', '#908a8a')
}
if (wordSpecialChar(password)) {
$('#rule_SECURITY_PASSWORD_SPECIAL_CHAR').css('color', 'green')
}
else {
$('#rule_SECURITY_PASSWORD_SPECIAL_CHAR').css('color', '#908a8a')
}
}
// 最小长度
function wordMinLength(word, minLength) {
//var minLength = {{ min_length }};
var re = new RegExp("^(.{" + minLength + ",})$");
return word.match(re)
}
// 大写字母
function wordUpperCase(word) {
return word.match(/([A-Z]+)/)
}
// 小写字母
function wordLowerCase(word) {
return word.match(/([a-z]+)/)
}
// 数字字符
function wordNumber(word) {
return word.match(/([\d]+)/)
}
// 特殊字符
function wordSpecialChar(word) {
return word.match(/[`,~,!,@,#,\$,%,\^,&,\*,\(,\),\-,_,=,\+,\{,\},\[,\],\|,\\,;,',:,",\,,\.,<,>,\/,\?]+/)
}
// 显示弹窗密码规则
function popoverPasswordRules(password_check_rules, $el) {
var message = "";
jQuery.each(password_check_rules, function (idx, rules) {
message += "<li id=" + rules.id + " style='list-style-type:none;'> <i class='fa fa-check-circle-o' style='margin-right:10px;' ></i>" + rules.label + "</li>";
});
//$('#id_password_rules').html(message);
$el.html(message)
}
// 初始化弹窗popover
function initPopover($container, $progress, $idPassword, $el, password_check_rules){
options = {};
// User Interface
options.ui = {
container: $container,
viewports: {
progress: $progress
//errors: $('.popover-content')
},
showProgressbar: true,
showVerdictsInsideProgressBar: true
};
$idPassword.pwstrength(options);
popoverPasswordRules(password_check_rules, $el);
}
/*!
* jQuery Password Strength plugin for Twitter Bootstrap
* Version: 2.2.1
*
* Copyright (c) 2008-2013 Tane Piper
* Copyright (c) 2013 Alejandro Blanco
* Dual licensed under the MIT and GPL licenses.
*/
(function (jQuery) {
// Source: src/i18n.js
var i18n = {};
(function (i18n, i18next) {
'use strict';
i18n.fallback = {
"wordMinLength": "Your password is too short",
"wordMaxLength": "Your password is too long",
"wordInvalidChar": "Your password contains an invalid character",
"wordNotEmail": "Do not use your email as your password",
"wordSimilarToUsername": "Your password cannot contain your username",
"wordTwoCharacterClasses": "Use different character classes",
"wordRepetitions": "Too many repetitions",
"wordSequences": "Your password contains sequences",
"errorList": "Errors:",
"veryWeak": "Very Weak",
"weak": "Weak",
"normal": "Normal",
"medium": "Medium",
"strong": "Strong",
"veryStrong": "Very Strong"
};
i18n.t = function (key) {
var result = '';
// Try to use i18next.com
if (i18next) {
result = i18next.t(key);
} else {
// Fallback to english
result = i18n.fallback[key];
}
return result === key ? '' : result;
};
}(i18n, window.i18next));
// Source: src/rules.js
var rulesEngine = {};
try {
if (!jQuery && module && module.exports) {
var jQuery = require("jquery"),
jsdom = require("jsdom").jsdom;
jQuery = jQuery(jsdom().defaultView);
}
} catch (ignore) {}
(function ($, rulesEngine) {
"use strict";
var validation = {};
rulesEngine.forbiddenSequences = [
"0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl",
"zxcvbnm", "!@#$%^&*()_+"
];
validation.wordNotEmail = function (options, word, score) {
if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) {
return score;
}
return 0;
};
validation.wordMinLength = function (options, word, score) {
var wordlen = word.length,
lenScore = Math.pow(wordlen, options.rules.raisePower);
if (wordlen < options.common.minChar) {
lenScore = (lenScore + score);
}
return lenScore;
};
validation.wordMaxLength = function (options, word, score) {
var wordlen = word.length,
lenScore = Math.pow(wordlen, options.rules.raisePower);
if (wordlen > options.common.maxChar) {
return score;
}
return lenScore;
};
validation.wordInvalidChar = function (options, word, score) {
if (options.common.invalidCharsRegExp.test(word)) {
return score;
}
return 0;
};
validation.wordMinLengthStaticScore = function (options, word, score) {
return word.length < options.common.minChar ? 0 : score;
};
validation.wordMaxLengthStaticScore = function (options, word, score) {
return word.length > options.common.maxChar ? 0 : score;
};
validation.wordSimilarToUsername = function (options, word, score) {
var username = $(options.common.usernameField).val();
if (username && word.toLowerCase().match(username.replace(/[\-\[\]\/\{\}\(\)\*\+\=\?\:\.\\\^\$\|\!\,]/g, "\\$&").toLowerCase())) {
return score;
}
return 0;
};
validation.wordTwoCharacterClasses = function (options, word, score) {
if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) ||
(word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) ||
(word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) {
return score;
}
return 0;
};
validation.wordRepetitions = function (options, word, score) {
if (word.match(/(.)\1\1/)) { return score; }
return 0;
};
validation.wordSequences = function (options, word, score) {
var found = false,
j;
if (word.length > 2) {
$.each(rulesEngine.forbiddenSequences, function (idx, seq) {
if (found) { return; }
var sequences = [seq, seq.split('').reverse().join('')];
$.each(sequences, function (idx, sequence) {
for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3:
if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) {
found = true;
}
}
});
});
if (found) { return score; }
}
return 0;
};
validation.wordLowercase = function (options, word, score) {
return word.match(/[a-z]/) && score;
};
validation.wordUppercase = function (options, word, score) {
return word.match(/[A-Z]/) && score;
};
validation.wordOneNumber = function (options, word, score) {
return word.match(/\d+/) && score;
};
validation.wordThreeNumbers = function (options, word, score) {
return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score;
};
validation.wordOneSpecialChar = function (options, word, score) {
return word.match(/[!,@,#,$,%,\^,&,*,?,_,~]/) && score;
};
validation.wordTwoSpecialChar = function (options, word, score) {
return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score;
};
validation.wordUpperLowerCombo = function (options, word, score) {
return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score;
};
validation.wordLetterNumberCombo = function (options, word, score) {
return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score;
};
validation.wordLetterNumberCharCombo = function (options, word, score) {
return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score;
};
validation.wordIsACommonPassword = function (options, word, score) {
if ($.inArray(word, options.rules.commonPasswords) >= 0) {
return score;
}
return 0;
};
rulesEngine.validation = validation;
rulesEngine.executeRules = function (options, word) {
var totalScore = 0;
$.each(options.rules.activated, function (rule, active) {
if (active) {
var score = options.rules.scores[rule],
funct = rulesEngine.validation[rule],
result,
errorMessage;
if (!$.isFunction(funct)) {
funct = options.rules.extra[rule];
}
if ($.isFunction(funct)) {
result = funct(options, word, score);
if (result) {
totalScore += result;
}
if (result < 0 || (!$.isNumeric(result) && !result)) {
errorMessage = options.ui.spanError(options, rule);
if (errorMessage.length > 0) {
options.instances.errors.push(errorMessage);
}
}
}
}
});
return totalScore;
};
}(jQuery, rulesEngine));
try {
if (module && module.exports) {
module.exports = rulesEngine;
}
} catch (ignore) {}
// Source: src/options.js
var defaultOptions = {};
defaultOptions.common = {};
defaultOptions.common.minChar = 6;
defaultOptions.common.maxChar = 20;
defaultOptions.common.usernameField = "#username";
defaultOptions.common.invalidCharsRegExp = new RegExp(/[\s,'"]/);
defaultOptions.common.userInputs = [
// Selectors for input fields with user input
];
defaultOptions.common.onLoad = undefined;
defaultOptions.common.onKeyUp = undefined;
defaultOptions.common.onScore = undefined;
defaultOptions.common.zxcvbn = false;
defaultOptions.common.zxcvbnTerms = [
// List of disrecommended words
];
defaultOptions.common.events = ["keyup", "change", "paste"];
defaultOptions.common.debug = false;
defaultOptions.rules = {};
defaultOptions.rules.extra = {};
defaultOptions.rules.scores = {
wordNotEmail: -100,
wordMinLength: -50,
wordMaxLength: -50,
wordInvalidChar: -100,
wordSimilarToUsername: -100,
wordSequences: -20,
wordTwoCharacterClasses: 2,
wordRepetitions: -25,
wordLowercase: 1,
wordUppercase: 3,
wordOneNumber: 3,
wordThreeNumbers: 5,
wordOneSpecialChar: 3,
wordTwoSpecialChar: 5,
wordUpperLowerCombo: 2,
wordLetterNumberCombo: 2,
wordLetterNumberCharCombo: 2,
wordIsACommonPassword: -100
};
defaultOptions.rules.activated = {
wordNotEmail: true,
wordMinLength: true,
wordMaxLength: false,
wordInvalidChar: false,
wordSimilarToUsername: true,
wordSequences: true,
wordTwoCharacterClasses: true,
wordRepetitions: true,
wordLowercase: true,
wordUppercase: true,
wordOneNumber: true,
wordThreeNumbers: true,
wordOneSpecialChar: true,
wordTwoSpecialChar: true,
wordUpperLowerCombo: true,
wordLetterNumberCombo: true,
wordLetterNumberCharCombo: true,
wordIsACommonPassword: true
};
defaultOptions.rules.raisePower = 1.4;
// List taken from https://github.com/danielmiessler/SecLists (MIT License)
defaultOptions.rules.commonPasswords = [
'123456',
'password',
'12345678',
'qwerty',
'123456789',
'12345',
'1234',
'111111',
'1234567',
'dragon',
'123123',
'baseball',
'abc123',
'football',
'monkey',
'letmein',
'696969',
'shadow',
'master',
'666666',
'qwertyuiop',
'123321',
'mustang',
'1234567890',
'michael',
'654321',
'pussy',
'superman',
'1qaz2wsx',
'7777777',
'fuckyou',
'121212',
'000000',
'qazwsx',
'123qwe',
'killer',
'trustno1',
'jordan',
'jennifer',
'zxcvbnm',
'asdfgh',
'hunter',
'buster',
'soccer',
'harley',
'batman',
'andrew',
'tigger',
'sunshine',
'iloveyou',
'fuckme',
'2000',
'charlie',
'robert',
'thomas',
'hockey',
'ranger',
'daniel',
'starwars',
'klaster',
'112233',
'george',
'asshole',
'computer',
'michelle',
'jessica',
'pepper',
'1111',
'zxcvbn',
'555555',
'11111111',
'131313',
'freedom',
'777777',
'pass',
'fuck',
'maggie',
'159753',
'aaaaaa',
'ginger',
'princess',
'joshua',
'cheese',
'amanda',
'summer',
'love',
'ashley',
'6969',
'nicole',
'chelsea',
'biteme',
'matthew',
'access',
'yankees',
'987654321',
'dallas',
'austin',
'thunder',
'taylor',
'matrix'
];
defaultOptions.ui = {};
defaultOptions.ui.bootstrap2 = false;
defaultOptions.ui.bootstrap4 = false;
defaultOptions.ui.colorClasses = [
"danger", "danger", "danger", "warning", "warning", "success"
];
defaultOptions.ui.showProgressBar = true;
defaultOptions.ui.progressBarEmptyPercentage = 1;
defaultOptions.ui.progressBarMinPercentage = 1;
defaultOptions.ui.progressExtraCssClasses = '';
defaultOptions.ui.progressBarExtraCssClasses = '';
defaultOptions.ui.showPopover = false;
defaultOptions.ui.popoverPlacement = "bottom";
defaultOptions.ui.showStatus = false;
defaultOptions.ui.spanError = function (options, key) {
"use strict";
var text = options.i18n.t(key);
if (!text) { return ''; }
return '<span style="color: #d52929">' + text + '</span>';
};
defaultOptions.ui.popoverError = function (options) {
"use strict";
var errors = options.instances.errors,
errorsTitle = options.i18n.t("errorList"),
message = "<div>" + errorsTitle + "<ul class='error-list' style='margin-bottom: 0;'>";
jQuery.each(errors, function (idx, err) {
message += "<li>" + err + "</li>";
});
message += "</ul></div>";
return message;
};
defaultOptions.ui.showVerdicts = true;
defaultOptions.ui.showVerdictsInsideProgressBar = false;
defaultOptions.ui.useVerdictCssClass = false;
defaultOptions.ui.showErrors = false;
defaultOptions.ui.showScore = false;
defaultOptions.ui.container = undefined;
defaultOptions.ui.viewports = {
progress: undefined,
verdict: undefined,
errors: undefined,
score: undefined
};
defaultOptions.ui.scores = [0, 14, 26, 38, 50];
defaultOptions.i18n = {};
defaultOptions.i18n.t = i18n.t;
// Source: src/ui.js
var ui = {};
(function ($, ui) {
"use strict";
var statusClasses = ["error", "warning", "success"],
verdictKeys = [
"veryWeak", "weak", "normal", "medium", "strong", "veryStrong"
];
ui.getContainer = function (options, $el) {
var $container;
$container = $(options.ui.container);
if (!($container && $container.length === 1)) {
$container = $el.parent();
}
return $container;
};
ui.findElement = function ($container, viewport, cssSelector) {
if (viewport) {
return $container.find(viewport).find(cssSelector);
}
return $container.find(cssSelector);
};
ui.getUIElements = function (options, $el) {
var $container, result;
if (options.instances.viewports) {
return options.instances.viewports;
}
$container = ui.getContainer(options, $el);
result = {};
result.$progressbar = ui.findElement($container, options.ui.viewports.progress, "div.progress");
if (options.ui.showVerdictsInsideProgressBar) {
result.$verdict = result.$progressbar.find("span.password-verdict");
}
if (!options.ui.showPopover) {
if (!options.ui.showVerdictsInsideProgressBar) {
result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict");
}
result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list");
}
result.$score = ui.findElement($container, options.ui.viewports.score,
"span.password-score");
options.instances.viewports = result;
return result;
};
ui.initProgressBar = function (options, $el) {
var $container = ui.getContainer(options, $el),
progressbar = "<div class='progress ";
if (options.ui.bootstrap2) {
// Boostrap 2
progressbar += options.ui.progressBarExtraCssClasses +
"'><div class='";
} else {
// Bootstrap 3 & 4
progressbar += options.ui.progressExtraCssClasses + "'><div class='" +
options.ui.progressBarExtraCssClasses + " progress-";
}
progressbar += "bar'>";
if (options.ui.showVerdictsInsideProgressBar) {
progressbar += "<span class='password-verdict'></span>";
}
progressbar += "</div></div>";
if (options.ui.viewports.progress) {
$container.find(options.ui.viewports.progress).append(progressbar);
} else {
$(progressbar).insertAfter($el);
}
};
ui.initHelper = function (options, $el, html, viewport) {
var $container = ui.getContainer(options, $el);
if (viewport) {
$container.find(viewport).append(html);
} else {
$(html).insertAfter($el);
}
};
ui.initVerdict = function (options, $el) {
ui.initHelper(options, $el, "<span class='password-verdict'></span>",
options.ui.viewports.verdict);
};
ui.initErrorList = function (options, $el) {
ui.initHelper(options, $el, "<ul class='error-list'></ul >",
options.ui.viewports.errors);
};
ui.initScore = function (options, $el) {
ui.initHelper(options, $el, "<span class='password-score'></span>",
options.ui.viewports.score);
};
ui.initPopover = function (options, $el) {
$el.popover("destroy");
$el.popover({
html: true,
placement: options.ui.popoverPlacement,
trigger: "manual",
content: " "
});
};
ui.initUI = function (options, $el) {
if (options.ui.showPopover) {
ui.initPopover(options, $el);
} else {
if (options.ui.showErrors) { ui.initErrorList(options, $el); }
if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
ui.initVerdict(options, $el);
}
}
if (options.ui.showProgressBar) {
ui.initProgressBar(options, $el);
}
if (options.ui.showScore) {
ui.initScore(options, $el);
}
};
ui.updateProgressBar = function (options, $el, cssClass, percentage) {
var $progressbar = ui.getUIElements(options, $el).$progressbar,
$bar = $progressbar.find(".progress-bar"),
cssPrefix = "progress-";
if (options.ui.bootstrap2) {
$bar = $progressbar.find(".bar");
cssPrefix = "";
}
$.each(options.ui.colorClasses, function (idx, value) {
if (options.ui.bootstrap4) {
$bar.removeClass("bg-" + value);
} else {
$bar.removeClass(cssPrefix + "bar-" + value);
}
});
if (options.ui.bootstrap4) {
$bar.addClass("bg-" + options.ui.colorClasses[cssClass]);
} else {
$bar.addClass(cssPrefix + "bar-" + options.ui.colorClasses[cssClass]);
}
$bar.css("width", percentage + '%');
};
ui.updateVerdict = function (options, $el, cssClass, text) {
var $verdict = ui.getUIElements(options, $el).$verdict;
$verdict.removeClass(options.ui.colorClasses.join(' '));
if (cssClass > -1) {
$verdict.addClass(options.ui.colorClasses[cssClass]);
}
if (options.ui.showVerdictsInsideProgressBar) {
$verdict.css('white-space', 'nowrap');
}
$verdict.html(text);
};
ui.updateErrors = function (options, $el, remove) {
var $errors = ui.getUIElements(options, $el).$errors,
html = "";
if (!remove) {
$.each(options.instances.errors, function (idx, err) {
html += "<li style='list-style-type:none;'>" + err + "</li>";
});
}
$errors.html(html);
};
ui.updateScore = function (options, $el, score, remove) {
var $score = ui.getUIElements(options, $el).$score,
html = "";
if (!remove) { html = score.toFixed(2); }
$score.html(html);
};
ui.updatePopover = function (options, $el, verdictText, remove) {
var popover = $el.data("bs.popover"),
html = "",
hide = true;
if (options.ui.showVerdicts &&
!options.ui.showVerdictsInsideProgressBar &&
verdictText.length > 0) {
html = "<h5><span class='password-verdict'>" + verdictText +
"</span></h5>";
hide = false;
}
if (options.ui.showErrors) {
if (options.instances.errors.length > 0) {
hide = false;
}
html += options.ui.popoverError(options);
}
if (hide || remove) {
$el.popover("hide");
return;
}
if (options.ui.bootstrap2) { popover = $el.data("popover"); }
if (popover.$arrow && popover.$arrow.parents("body").length > 0) {
$el.find("+ .popover .popover-content").html(html);
} else {
// It's hidden
popover.options.content = html;
$el.popover("show");
}
};
ui.updateFieldStatus = function (options, $el, cssClass, remove) {
var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group",
$container = $el.parents(targetClass).first();
$.each(statusClasses, function (idx, css) {
if (!options.ui.bootstrap2) { css = "has-" + css; }
$container.removeClass(css);
});
if (remove) { return; }
cssClass = statusClasses[Math.floor(cssClass / 2)];
if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; }
$container.addClass(cssClass);
};
ui.percentage = function (options, score, maximun) {
var result = Math.floor(100 * score / maximun),
min = options.ui.progressBarMinPercentage;
result = result <= min ? min : result;
result = result > 100 ? 100 : result;
return result;
};
ui.getVerdictAndCssClass = function (options, score) {
var level, verdict;
if (score === undefined) { return ['', 0]; }
if (score <= options.ui.scores[0]) {
level = 0;
} else if (score < options.ui.scores[1]) {
level = 1;
} else if (score < options.ui.scores[2]) {
level = 2;
} else if (score < options.ui.scores[3]) {
level = 3;
} else if (score < options.ui.scores[4]) {
level = 4;
} else {
level = 5;
}
verdict = verdictKeys[level];
return [options.i18n.t(verdict), level];
};
ui.updateUI = function (options, $el, score) {
var cssClass, barPercentage, verdictText, verdictCssClass;
cssClass = ui.getVerdictAndCssClass(options, score);
verdictText = score === 0 ? '' : cssClass[0];
cssClass = cssClass[1];
verdictCssClass = options.ui.useVerdictCssClass ? cssClass : -1;
if (options.ui.showProgressBar) {
if (score === undefined) {
barPercentage = options.ui.progressBarEmptyPercentage;
} else {
barPercentage = ui.percentage(options, score, options.ui.scores[4]);
}
ui.updateProgressBar(options, $el, cssClass, barPercentage);
if (options.ui.showVerdictsInsideProgressBar) {
ui.updateVerdict(options, $el, verdictCssClass, verdictText);
}
}
if (options.ui.showStatus) {
ui.updateFieldStatus(options, $el, cssClass, score === undefined);
}
if (options.ui.showPopover) {
ui.updatePopover(options, $el, verdictText, score === undefined);
} else {
if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
ui.updateVerdict(options, $el, verdictCssClass, verdictText);
}
if (options.ui.showErrors) {
ui.updateErrors(options, $el, score === undefined);
}
}
if (options.ui.showScore) {
ui.updateScore(options, $el, score, score === undefined);
}
};
}(jQuery, ui));
// Source: src/methods.js
var methods = {};
(function ($, methods) {
"use strict";
var onKeyUp, onPaste, applyToAll;
onKeyUp = function (event) {
var $el = $(event.target),
options = $el.data("pwstrength-bootstrap"),
word = $el.val(),
userInputs,
verdictText,
verdictLevel,
score;
if (options === undefined) { return; }
options.instances.errors = [];
if (word.length === 0) {
score = undefined;
} else {
if (options.common.zxcvbn) {
userInputs = [];
$.each(options.common.userInputs.concat([options.common.usernameField]), function (idx, selector) {
var value = $(selector).val();
if (value) { userInputs.push(value); }
});
userInputs = userInputs.concat(options.common.zxcvbnTerms);
score = zxcvbn(word, userInputs).guesses;
score = Math.log(score) * Math.LOG2E;
} else {
score = rulesEngine.executeRules(options, word);
}
if ($.isFunction(options.common.onScore)) {
score = options.common.onScore(options, word, score);
}
}
ui.updateUI(options, $el, score);
verdictText = ui.getVerdictAndCssClass(options, score);
verdictLevel = verdictText[1];
verdictText = verdictText[0];
if (options.common.debug) {
console.log(score + ' - ' + verdictText);
}
if ($.isFunction(options.common.onKeyUp)) {
options.common.onKeyUp(event, {
score: score,
verdictText: verdictText,
verdictLevel: verdictLevel
});
}
};
onPaste = function (event) {
// This handler is necessary because the paste event fires before the
// content is actually in the input, so we cannot read its value right
// away. Therefore, the timeouts.
var $el = $(event.target),
word = $el.val(),
tries = 0,
callback;
callback = function () {
var newWord = $el.val();
if (newWord !== word) {
onKeyUp(event);
} else if (tries < 3) {
tries += 1;
setTimeout(callback, 100);
}
};
setTimeout(callback, 100);
};
methods.init = function (settings) {
this.each(function (idx, el) {
// Make it deep extend (first param) so it extends also the
// rules and other inside objects
var clonedDefaults = $.extend(true, {}, defaultOptions),
localOptions = $.extend(true, clonedDefaults, settings),
$el = $(el);
localOptions.instances = {};
$el.data("pwstrength-bootstrap", localOptions);
$.each(localOptions.common.events, function (idx, eventName) {
var handler = eventName === "paste" ? onPaste : onKeyUp;
$el.on(eventName, handler);
});
ui.initUI(localOptions, $el);
$el.trigger("keyup");
if ($.isFunction(localOptions.common.onLoad)) {
localOptions.common.onLoad();
}
});
return this;
};
methods.destroy = function () {
this.each(function (idx, el) {
var $el = $(el),
options = $el.data("pwstrength-bootstrap"),
elements = ui.getUIElements(options, $el);
elements.$progressbar.remove();
elements.$verdict.remove();
elements.$errors.remove();
$el.removeData("pwstrength-bootstrap");
});
};
methods.forceUpdate = function () {
this.each(function (idx, el) {
var event = { target: el };
onKeyUp(event);
});
};
methods.addRule = function (name, method, score, active) {
this.each(function (idx, el) {
var options = $(el).data("pwstrength-bootstrap");
options.rules.activated[name] = active;
options.rules.scores[name] = score;
options.rules.extra[name] = method;
});
};
applyToAll = function (rule, prop, value) {
this.each(function (idx, el) {
$(el).data("pwstrength-bootstrap").rules[prop][rule] = value;
});
};
methods.changeScore = function (rule, score) {
applyToAll.call(this, rule, "scores", score);
};
methods.ruleActive = function (rule, active) {
applyToAll.call(this, rule, "activated", active);
};
methods.ruleIsMet = function (rule) {
if ($.isFunction(rulesEngine.validation[rule])) {
if (rule === "wordMinLength") {
rule = "wordMinLengthStaticScore";
} else if (rule === "wordMaxLength") {
rule = "wordMaxLengthStaticScore";
}
var rulesMetCnt = 0;
this.each(function (idx, el) {
var options = $(el).data("pwstrength-bootstrap");
rulesMetCnt += rulesEngine.validation[rule](options, $(el).val(), 1);
});
return (rulesMetCnt === this.length);
}
$.error("Rule " + rule + " does not exist on jQuery.pwstrength-bootstrap.validation");
};
$.fn.pwstrength = function (method) {
var result;
if (methods[method]) {
result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === "object" || !method) {
result = methods.init.apply(this, arguments);
} else {
$.error("Method " + method + " does not exist on jQuery.pwstrength-bootstrap");
}
return result;
};
}(jQuery, methods));
}(jQuery));
\ No newline at end of file
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script> <script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
{% block custom_head_css_js_create %} {% endblock %} {% block custom_head_css_js_create %} {% endblock %}
{% endblock %} {% endblock %}
......
<div class="footer fixed"> <div class="footer fixed">
<div class="pull-right"> <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"> <!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div> </div>
<div> <div>
<strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2018 <strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2018
......
...@@ -16,13 +16,14 @@ class UserLoginForm(AuthenticationForm): ...@@ -16,13 +16,14 @@ class UserLoginForm(AuthenticationForm):
max_length=128, strip=False 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) class UserLoginCaptchaForm(UserLoginForm):
password = forms.CharField(
label=_('Password'), widget=forms.PasswordInput,
max_length=128, strip=False
)
captcha = CaptchaField() captcha = CaptchaField()
...@@ -72,7 +73,7 @@ class UserCreateUpdateForm(forms.ModelForm): ...@@ -72,7 +73,7 @@ class UserCreateUpdateForm(forms.ModelForm):
'data-placeholder': _('Join user groups') 'data-placeholder': _('Join user groups')
} }
), ),
'otp_level': forms.RadioSelect() 'otp_level': forms.RadioSelect(),
} }
def clean_public_key(self): def clean_public_key(self):
......
...@@ -4,12 +4,10 @@ import uuid ...@@ -4,12 +4,10 @@ import uuid
from django.db import models, IntegrityError from django.db import models, IntegrityError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.mixins import NoDeleteModelMixin
__all__ = ['UserGroup'] __all__ = ['UserGroup']
class UserGroup(NoDeleteModelMixin): class UserGroup(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
comment = models.TextField(blank=True, verbose_name=_('Comment')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
......
...@@ -14,6 +14,7 @@ from django.utils import timezone ...@@ -14,6 +14,7 @@ from django.utils import timezone
from django.shortcuts import reverse from django.shortcuts import reverse
from common.utils import get_signer, date_expired_default from common.utils import get_signer, date_expired_default
from common.models import Setting
__all__ = ['User'] __all__ = ['User']
...@@ -35,6 +36,12 @@ class User(AbstractUser): ...@@ -35,6 +36,12 @@ class User(AbstractUser):
(1, _('Enable')), (1, _('Enable')),
(2, _("Force 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) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField( username = models.CharField(
max_length=128, unique=True, verbose_name=_('Username') max_length=128, unique=True, verbose_name=_('Username')
...@@ -82,6 +89,10 @@ class User(AbstractUser): ...@@ -82,6 +89,10 @@ class User(AbstractUser):
created_by = models.CharField( created_by = models.CharField(
max_length=30, default='', verbose_name=_('Created by') 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): def __str__(self):
return '{0.name}({0.username})'.format(self) return '{0.name}({0.username})'.format(self)
...@@ -248,14 +259,17 @@ class User(AbstractUser): ...@@ -248,14 +259,17 @@ class User(AbstractUser):
@property @property
def otp_enabled(self): def otp_enabled(self):
return self.otp_level > 0 return self.otp_force_enabled or self.otp_level > 0
@property @property
def otp_force_enabled(self): 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 return self.otp_level == 2
def enable_otp(self): def enable_otp(self):
if not self.otp_force_enabled: if not self.otp_level == 2:
self.otp_level = 1 self.otp_level = 1
def force_enable_otp(self): def force_enable_otp(self):
...@@ -275,6 +289,7 @@ class User(AbstractUser): ...@@ -275,6 +289,7 @@ class User(AbstractUser):
'is_superuser': self.is_superuser, 'is_superuser': self.is_superuser,
'role': self.get_role_display(), 'role': self.get_role_display(),
'groups': [group.name for group in self.groups.all()], 'groups': [group.name for group in self.groups.all()],
'source': self.get_source_display(),
'wechat': self.wechat, 'wechat': self.wechat,
'phone': self.phone, 'phone': self.phone,
'otp_level': self.otp_level, 'otp_level': self.otp_level,
......
...@@ -26,7 +26,10 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -26,7 +26,10 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
def get_field_names(self, declared_fields, info): def get_field_names(self, declared_fields, info):
fields = super(UserSerializer, self).get_field_names(declared_fields, info) fields = super(UserSerializer, self).get_field_names(declared_fields, info)
fields.extend(['groups_display', 'get_role_display', 'is_valid']) fields.extend([
'groups_display', 'get_role_display',
'get_source_display', 'is_valid'
])
return fields return fields
@staticmethod @staticmethod
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# #
from django.dispatch import receiver from django.dispatch import receiver
from django_auth_ldap.backend import populate_user
# from django.db.models.signals import post_save # from django.db.models.signals import post_save
from common.utils import get_logger from common.utils import get_logger
...@@ -28,3 +29,11 @@ def on_user_create(sender, user=None, **kwargs): ...@@ -28,3 +29,11 @@ def on_user_create(sender, user=None, **kwargs):
logger.info(" - Sending welcome mail ...".format(user.name)) logger.info(" - Sending welcome mail ...".format(user.name))
if user.email: if user.email:
send_user_created_mail(user) 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 @@ ...@@ -48,6 +48,7 @@
</div> </div>
</div> </div>
</form> </form>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script> <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
......
...@@ -122,10 +122,10 @@ ...@@ -122,10 +122,10 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
$(document).on('click', ".fl_goto", function(){ $(document).on('click', ".fl_goto", function(){
var $form = $('#fl_form'); var $form = $('#fl_form');
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form); $('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
$form.submit(); $form.submit();
return false; return false;
}).on('click', '#fl_submit', function(){ }).on('click', '#fl_submit', function(){
var isFinish = $('#fl_submit').html() === "{% trans 'Finish' %}"; var isFinish = $('#fl_submit').html() === "{% trans 'Finish' %}";
var noChecked = !$('#acceptTerms').prop('checked'); var noChecked = !$('#acceptTerms').prop('checked');
...@@ -137,9 +137,10 @@ ...@@ -137,9 +137,10 @@
return false; return false;
} }
}).on('click', '#btn-reset-pubkey', function () { }).on('click', '#btn-reset-pubkey', function () {
var the_url = '{% url "users:user-pubkey-generate" %}'; var the_url = '{% url "users:user-pubkey-generate" %}';
window.open(the_url, "_blank") window.open(the_url, "_blank");
}) $('#fl_form').submit();
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script src="{% static "js/jumpserver.js" %}"></script> <script src="{% static "js/jumpserver.js" %}"></script>
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
</head> </head>
...@@ -49,10 +50,20 @@ ...@@ -49,10 +50,20 @@
<p class="red-fonts">{{ errors }}</p> <p class="red-fonts">{{ errors }}</p>
{% endif %} {% endif %}
<div class="form-group"> <div class="form-group">
<input type="password" class="form-control" name="password" placeholder="{% trans 'Password' %}" required=""> <input type="password" id="id_new_password" class="form-control" name="password" placeholder="{% trans 'Password' %}" required="">
{# 密码popover #}
<div id="container">
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
<div class="arrow" style="left: 50%;"></div>
<h3 class="popover-title" style="display: none;"></h3>
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
<div class="popover-content"></div>
</div>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="password" class="form-control" name="password-confirm" placeholder="{% trans 'Password again' %}" required=""> <input type="password" id="id_confirm_password" class="form-control" name="password-confirm" placeholder="{% trans 'Password again' %}" required="">
</div> </div>
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button> <button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
...@@ -79,4 +90,33 @@ ...@@ -79,4 +90,33 @@
</body> </body>
</html> </html>
<script>
$(document).ready(function () {
// 密码强度校验
var el = $('#id_password_rules'),
idPassword = $('#id_new_password'),
idPopover = $('#popover777'),
container = $('#container'),
progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }},
minLength = {{ min_length }},
top = 146, left = 170;
// 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules);
// 监听事件
idPassword.on('focus', function () {
idPopover.css('top', top);
idPopover.css('left', left);
idPopover.css('display', 'block');
});
idPassword.on('blur', function () {
idPopover.css('display', 'none');
});
idPassword.on('keyup', function(){
var password = idPassword.val();
checkPasswordRules(password, minLength);
})
})
</script>
...@@ -99,6 +99,10 @@ ...@@ -99,6 +99,10 @@
{% endif %} {% endif %}
</b></td> </b></td>
</tr> </tr>
<tr>
<td>{% trans 'Source' %}:</td>
<td><b>{{ user_object.get_source_display }}</b></td>
</tr>
<tr> <tr>
<td>{% trans 'Date expired' %}:</td> <td>{% trans 'Date expired' %}:</td>
<td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td> <td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td>
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
<th class="text-center">{% trans 'Username' %}</th> <th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Role' %}</th> <th class="text-center">{% trans 'Role' %}</th>
<th class="text-center">{% trans 'User group' %}</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 'Active' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
...@@ -65,14 +66,14 @@ function initTable() { ...@@ -65,14 +66,14 @@ function initTable() {
var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData; var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData;
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); $(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) { if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>') $(td).html('<i class="fa fa-times text-danger"></i>')
} else { } else {
$(td).html('<i class="fa fa-check text-navy"></i>') $(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 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 = ""; var del_btn = "";
...@@ -90,7 +91,7 @@ function initTable() { ...@@ -90,7 +91,7 @@ function initTable() {
ajax_url: '{% url "api-users:user-list" %}', ajax_url: '{% url "api-users:user-list" %}',
columns: [ columns: [
{data: "id"}, {data: "name" }, {data: "username" }, {data: "get_role_display" }, {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() op_html: $('#actions').html()
}; };
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
<link href="{% static "css/plugins/cropper/cropper.min.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/cropper/cropper.min.css" %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script> <script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script>
<style> <style>
.crop { .crop {
...@@ -50,6 +52,16 @@ ...@@ -50,6 +52,16 @@
{% csrf_token %} {% csrf_token %}
{% bootstrap_field form.old_password layout="horizontal" %} {% bootstrap_field form.old_password layout="horizontal" %}
{% bootstrap_field form.new_password layout="horizontal" %} {% bootstrap_field form.new_password layout="horizontal" %}
{# 密码popover #}
<div id="container">
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
<div class="arrow" style="left: 50%;"></div>
<h3 class="popover-title" style="display: none;"></h3>
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
<div class="popover-content"></div>
</div>
</div>
{% bootstrap_field form.confirm_password layout="horizontal" %} {% bootstrap_field form.confirm_password layout="horizontal" %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
...@@ -71,5 +83,34 @@ ...@@ -71,5 +83,34 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script> <script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script>
<script> <script>
$(document).ready(function () {
var el = $('#id_password_rules'),
idPassword = $('#id_new_password'),
idPopover = $('#popover777'),
container = $('#container'),
progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }},
minLength = {{ min_length }},
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34,
left = 377;
// 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules);
// 监听事件
idPassword.on('focus', function () {
idPopover.css('top', top);
idPopover.css('left', left);
idPopover.css('display', 'block');
});
idPassword.on('blur', function () {
idPopover.css('display', 'none');
});
idPassword.on('keyup', function(){
var password = idPassword.val();
checkPasswordRules(password, minLength);
});
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -91,8 +91,15 @@ ...@@ -91,8 +91,15 @@
{% else %} {% else %}
{% trans 'Disable' %} {% trans 'Disable' %}
{% endif %} {% endif %}
{% if mfa_setting %}
( {% trans 'Administrator Settings force MFA login' %} )
{% endif %}
</td> </td>
</tr> </tr>
<tr>
<td class="text-navy">{% trans 'Source' %}</td>
<td>{{ user.get_source_display }}</td>
</tr>
<tr> <tr>
<td class="text-navy">{% trans 'Date joined' %}</td> <td class="text-navy">{% trans 'Date joined' %}</td>
<td>{{ user.date_joined|date:"Y-m-d H:i:s" }}</td> <td>{{ user.date_joined|date:"Y-m-d H:i:s" }}</td>
......
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2 col-lg-2" style="padding-top: 0">{% trans 'Or reset by server' %}</label> <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 "> <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> </div>
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
...@@ -89,5 +89,10 @@ ...@@ -89,5 +89,10 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script> <script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script>
<script> <script>
$(document).ready(function () {
}).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> </script>
{% endblock %} {% endblock %}
...@@ -4,5 +4,50 @@ ...@@ -4,5 +4,50 @@
{% block user_template_title %}{% trans "Update user" %}{% endblock %} {% block user_template_title %}{% trans "Update user" %}{% endblock %}
{% block password %} {% block password %}
{% bootstrap_field form.password layout="horizontal" %} {% bootstrap_field form.password layout="horizontal" %}
{# 密码popover #}
<div id="container">
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
<div class="arrow" style="left: 50%;"></div>
<h3 class="popover-title" style="display: none;"></h3>
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
<div class="popover-content"></div>
</div>
</div>
{% bootstrap_field form.public_key layout="horizontal" %} {% bootstrap_field form.public_key layout="horizontal" %}
{% endblock %} {% endblock %}
{% block custom_foot_js %}
{{ block.super }}
<script>
$(document).ready(function(){
var el = $('#id_password_rules'),
idPassword = $('#id_password'),
idPopover = $('#popover777'),
container = $('#container'),
progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }},
minLength = {{ min_length }},
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34,
left = 377;
// 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules);
// 监听事件
idPassword.on('focus', function () {
idPopover.css('top', top);
idPopover.css('left', left);
idPopover.css('display', 'block');
});
idPassword.on('blur', function () {
idPopover.css('display', 'none');
});
idPassword.on('keyup', function(){
var password = idPassword.val();
checkPasswordRules(password, minLength);
});
})
</script>
{% endblock %}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# #
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import re
import pyotp import pyotp
import base64 import base64
import logging import logging
...@@ -18,8 +19,11 @@ from django.core.cache import cache ...@@ -18,8 +19,11 @@ from django.core.cache import cache
from common.tasks import send_mail_async from common.tasks import send_mail_async
from common.utils import reverse, get_object_or_none from common.utils import reverse, get_object_or_none
from common.models import Setting
from common.forms import SecuritySettingForm
from .models import User, LoginLog from .models import User, LoginLog
logger = logging.getLogger('jumpserver') logger = logging.getLogger('jumpserver')
...@@ -271,3 +275,60 @@ def generate_otp_uri(request, issuer="Jumpserver"): ...@@ -271,3 +275,60 @@ def generate_otp_uri(request, issuer="Jumpserver"):
def check_otp_code(otp_secret_key, otp_code): def check_otp_code(otp_secret_key, otp_code):
totp = pyotp.TOTP(otp_secret_key) totp = pyotp.TOTP(otp_secret_key)
return totp.verify(otp_code) return totp.verify(otp_code)
def get_password_check_rules():
check_rules = []
min_length = settings.DEFAULT_PASSWORD_MIN_LENGTH
min_name = 'SECURITY_PASSWORD_MIN_LENGTH'
base_filed = SecuritySettingForm.base_fields
password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD')
if not password_setting:
# 用户还没有设置过密码校验规则
label = base_filed.get(min_name).label
label += ' ' + str(min_length) + _('Bit')
id = 'rule_' + min_name
rules = {'id': id, 'label': label}
check_rules.append(rules)
for setting in password_setting:
if setting.cleaned_value:
id = 'rule_' + setting.name
label = base_filed.get(setting.name).label
if setting.name == min_name:
label += str(setting.cleaned_value) + _('Bit')
min_length = setting.cleaned_value
rules = {'id': id, 'label': label}
check_rules.append(rules)
return check_rules, min_length
def check_password_rules(password):
min_field_name = 'SECURITY_PASSWORD_MIN_LENGTH'
upper_field_name = 'SECURITY_PASSWORD_UPPER_CASE'
lower_field_name = 'SECURITY_PASSWORD_LOWER_CASE'
number_field_name = 'SECURITY_PASSWORD_NUMBER'
special_field_name = 'SECURITY_PASSWORD_SPECIAL_CHAR'
min_length_setting = Setting.objects.filter(name=min_field_name).first()
min_length = min_length_setting.value if min_length_setting else settings.DEFAULT_PASSWORD_MIN_LENGTH
password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD')
if not password_setting:
pattern = r"^.{" + str(min_length) + ",}$"
else:
pattern = r"^"
for setting in password_setting:
if setting.cleaned_value and setting.name == upper_field_name:
pattern += '(?=.*[A-Z])'
elif setting.cleaned_value and setting.name == lower_field_name:
pattern += '(?=.*[a-z])'
elif setting.cleaned_value and setting.name == number_field_name:
pattern += '(?=.*\d)'
elif setting.cleaned_value and setting.name == special_field_name:
pattern += '(?=.*[`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'",\.<>\/\?])'
pattern += '[a-zA-Z\d`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'",\.<>\/\?]'
match_obj = re.match(pattern, password)
return bool(match_obj)
...@@ -23,9 +23,10 @@ from django.conf import settings ...@@ -23,9 +23,10 @@ from django.conf import settings
from common.utils import get_object_or_none from common.utils import get_object_or_none
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from common.models import Setting
from ..models import User, LoginLog from ..models import User, LoginLog
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, redirect_user_first_login_or_index, \ from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, redirect_user_first_login_or_index, \
get_user_or_tmp_user, set_tmp_user_to_cache get_user_or_tmp_user, set_tmp_user_to_cache, get_password_check_rules, check_password_rules
from ..tasks import write_login_log_async from ..tasks import write_login_log_async
from .. import forms from .. import forms
...@@ -82,10 +83,10 @@ class UserLoginView(FormView): ...@@ -82,10 +83,10 @@ class UserLoginView(FormView):
user = get_user_or_tmp_user(self.request) user = get_user_or_tmp_user(self.request)
if user.otp_enabled and user.otp_secret_key: if user.otp_enabled and user.otp_secret_key:
# 1,2 & T # 1,2,mfa_setting & T
return reverse('users:login-otp') return reverse('users:login-otp')
elif user.otp_enabled and not user.otp_secret_key: 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') return reverse('users:user-otp-enable-authentication')
elif not user.otp_enabled: elif not user.otp_enabled:
# 0 & T,F # 0 & T,F
...@@ -211,6 +212,10 @@ class UserResetPasswordView(TemplateView): ...@@ -211,6 +212,10 @@ class UserResetPasswordView(TemplateView):
token = request.GET.get('token') token = request.GET.get('token')
user = User.validate_reset_token(token) user = User.validate_reset_token(token)
check_rules, min_length = get_password_check_rules()
password_rules = {'password_check_rules': check_rules, 'min_length': min_length}
kwargs.update(password_rules)
if not user: if not user:
kwargs.update({'errors': _('Token invalid or expired')}) kwargs.update({'errors': _('Token invalid or expired')})
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
...@@ -227,6 +232,13 @@ class UserResetPasswordView(TemplateView): ...@@ -227,6 +232,13 @@ class UserResetPasswordView(TemplateView):
if not user: if not user:
return self.get(request, errors=_('Token invalid or expired')) return self.get(request, errors=_('Token invalid or expired'))
is_ok = check_password_rules(password)
if not is_ok:
return self.get(
request,
errors=_('* Your password does not meet the requirements')
)
user.reset_password(password) user.reset_password(password)
return HttpResponseRedirect(reverse('users:reset-password-success')) return HttpResponseRedirect(reverse('users:reset-password-success'))
......
...@@ -33,9 +33,10 @@ from django.contrib.auth import logout as auth_logout ...@@ -33,9 +33,10 @@ from django.contrib.auth import logout as auth_logout
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
from common.models import Setting
from .. import forms from .. import forms
from ..models import User, UserGroup from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, get_user_or_tmp_user from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, get_user_or_tmp_user, get_password_check_rules, check_password_rules
from ..signals import post_user_create from ..signals import post_user_create
from ..tasks import write_login_log_async from ..tasks import write_login_log_async
...@@ -96,10 +97,29 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -96,10 +97,29 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
success_message = update_success_msg success_message = update_success_msg
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = {'app': _('Users'), 'action': _('Update user')} check_rules, min_length = get_password_check_rules()
context = {
'app': _('Users'),
'action': _('Update user'),
'password_check_rules': check_rules,
'min_length': min_length
}
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def form_valid(self, form):
password = form.cleaned_data.get('password')
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): class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView):
model = User model = User
...@@ -318,8 +338,10 @@ class UserProfileView(LoginRequiredMixin, TemplateView): ...@@ -318,8 +338,10 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
template_name = 'users/user_profile.html' template_name = 'users/user_profile.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
mfa_setting = Setting.objects.filter(name='SECURITY_MFA_AUTH').first()
context = { context = {
'action': _('Profile'), 'action': _('Profile'),
'mfa_setting': mfa_setting.cleaned_value if mfa_setting else False,
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -353,9 +375,12 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView): ...@@ -353,9 +375,12 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
return self.request.user return self.request.user
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
check_rules, min_length = get_password_check_rules()
context = { context = {
'app': _('Users'), 'app': _('Users'),
'action': _('Password update'), 'action': _('Password update'),
'password_check_rules': check_rules,
'min_length': min_length,
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -364,6 +389,17 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView): ...@@ -364,6 +389,17 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
auth_logout(self.request) auth_logout(self.request)
return super().get_success_url() return super().get_success_url()
def form_valid(self, form):
password = form.cleaned_data.get('new_password')
is_ok = check_password_rules(password)
if not is_ok:
form.add_error(
"new_password",
_("* Your password does not meet the requirements")
)
return self.form_invalid(form)
return super().form_valid(form)
class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView): class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView):
template_name = 'users/user_pubkey_update.html' template_name = 'users/user_pubkey_update.html'
......
...@@ -51,11 +51,6 @@ class Config: ...@@ -51,11 +51,6 @@ class Config:
REDIS_HOST = '127.0.0.1' REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379 REDIS_PORT = 6379
REDIS_PASSWORD = '' REDIS_PASSWORD = ''
BROKER_URL = 'redis://%(password)s%(host)s:%(port)s/3' % {
'password': REDIS_PASSWORD,
'host': REDIS_HOST,
'port': REDIS_PORT,
}
def __init__(self): def __init__(self):
pass pass
......
...@@ -17,9 +17,14 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings") ...@@ -17,9 +17,14 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
django.setup() django.setup()
from users.models import UserGroup from users.models import UserGroup
from django.core.exceptions import FieldError
def clean_group(interactive=True): def clean_group(interactive=True):
try:
UserGroup.objects.all().filter(is_discard=True).delete()
except FieldError:
pass
groups = UserGroup.objects.all() groups = UserGroup.objects.all()
groups_name_list = groups.values_list('name', flat=True) groups_name_list = groups.values_list('name', flat=True)
groups_with_info = groups.annotate(Count('users'))\ groups_with_info = groups.annotate(Count('users'))\
...@@ -50,7 +55,7 @@ def clean_group(interactive=True): ...@@ -50,7 +55,7 @@ def clean_group(interactive=True):
"Delete user group <{}>, create at {}? ([y]/n)".format( "Delete user group <{}>, create at {}? ([y]/n)".format(
name, group.date_created) name, group.date_created)
) )
if confirm.lower() == "y": if confirm.lower() in ["y", ""]:
confirm = True confirm = True
break break
elif confirm.lower() == "n": elif confirm.lower() == "n":
......
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