mirror of https://github.com/jumpserver/jumpserver
commit
e7a731fae9
|
@ -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):
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SecuritySettingForm(BaseForm):
|
||||||
|
# MFA全局设置
|
||||||
|
SECURITY_MFA_AUTH = forms.BooleanField(
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("MFA Secondary certification"),
|
||||||
|
help_text=_(
|
||||||
|
'After opening, the user login must use MFA secondary '
|
||||||
|
'authentication (valid for all users, including administrators)'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# 最小长度
|
||||||
|
SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
|
||||||
|
initial=6, label=_("Password minimum length"),
|
||||||
|
min_value=6
|
||||||
|
)
|
||||||
|
# 大写字母
|
||||||
|
SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField(
|
||||||
|
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("Must contain capital letters"),
|
||||||
|
help_text=_(
|
||||||
|
'After opening, the user password changes '
|
||||||
|
'and resets must contain uppercase letters')
|
||||||
|
)
|
||||||
|
# 小写字母
|
||||||
|
SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField(
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("Must contain lowercase letters"),
|
||||||
|
help_text=_('After opening, the user password changes '
|
||||||
|
'and resets must contain lowercase letters')
|
||||||
|
)
|
||||||
|
# 数字
|
||||||
|
SECURITY_PASSWORD_NUMBER = forms.BooleanField(
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("Must contain numeric characters"),
|
||||||
|
help_text=_('After opening, the user password changes '
|
||||||
|
'and resets must contain numeric characters')
|
||||||
|
)
|
||||||
|
# 特殊字符
|
||||||
|
SECURITY_PASSWORD_SPECIAL_CHAR= forms.BooleanField(
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("Must contain special characters"),
|
||||||
|
help_text=_('After opening, the user password changes '
|
||||||
|
'and resets must contain special characters')
|
||||||
|
)
|
||||||
|
|
|
@ -34,7 +34,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
|
||||||
def ldap_auth_on_changed(sender, enabled=True, **kwargs):
|
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 @@
|
||||||
<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 @@
|
||||||
<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 @@
|
||||||
<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">
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load common_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="ibox float-e-margins">
|
||||||
|
<div class="panel-options">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||||
|
</li>
|
||||||
|
<li class="active">
|
||||||
|
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="col-sm-12" style="padding-left:0">
|
||||||
|
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||||
|
<form action="" method="post" class="form-horizontal">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<h3>{% trans "MFA setting" %}</h3>
|
||||||
|
{% for field in form %}
|
||||||
|
{% if forloop.counter == 2 %}
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
<h3>{% trans "Password check rule" %}</h3>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not field.field|is_bool_field %}
|
||||||
|
{% bootstrap_field field layout="horizontal" %}
|
||||||
|
{% else %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<div class="col-sm-1">
|
||||||
|
{{ field }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<span class="help-block" >{{ field.help_text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
|
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||||
|
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -27,6 +27,9 @@
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
|
<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 @@
|
||||||
</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 @@
|
||||||
{% 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 = [
|
||||||
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 _
|
||||||
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):
|
||||||
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):
|
||||||
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)
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -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):
|
||||||
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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,976 @@
|
||||||
|
/*!
|
||||||
|
* 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));
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<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>
|
||||||
|
|
|
@ -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):
|
||||||
'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):
|
||||||
|
|
|
@ -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):
|
||||||
(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):
|
||||||
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):
|
||||||
|
|
||||||
@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):
|
||||||
'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):
|
||||||
|
|
||||||
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 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
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):
|
||||||
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 @@
|
||||||
</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 @@
|
||||||
{% 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 @@
|
||||||
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 @@
|
||||||
{% 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 @@
|
||||||
<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 @@
|
||||||
</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 @@
|
||||||
{% 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 @@
|
||||||
<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() {
|
||||||
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() {
|
||||||
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 @@
|
||||||
<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 @@
|
||||||
{% 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 @@
|
||||||
{% 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 @@
|
||||||
{% 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 @@
|
||||||
<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 @@
|
||||||
{% 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 @@
|
||||||
{% 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 @@
|
||||||
#
|
#
|
||||||
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
|
||||||
|
|
||||||
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"):
|
||||||
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
|
||||||
|
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
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
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
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:
|
||||||
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
|
||||||
|
|
Loading…
Reference in New Issue