Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
e7a731fa
Unverified
Commit
e7a731fa
authored
Jun 11, 2018
by
老广
Committed by
GitHub
Jun 11, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1417 from jumpserver/dev
Dev
parents
283b1c1d
73f9f546
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
37 changed files
with
579 additions
and
37 deletions
+579
-37
__init__.py
apps/__init__.py
+1
-1
node.py
apps/assets/api/node.py
+3
-2
utils.py
apps/assets/utils.py
+0
-1
forms.py
apps/common/forms.py
+46
-0
signals_handler.py
apps/common/signals_handler.py
+1
-1
basic_setting.html
apps/common/templates/common/basic_setting.html
+3
-0
email_setting.html
apps/common/templates/common/email_setting.html
+3
-0
ldap_setting.html
apps/common/templates/common/ldap_setting.html
+3
-0
security_setting.html
apps/common/templates/common/security_setting.html
+87
-0
terminal_setting.html
apps/common/templates/common/terminal_setting.html
+5
-0
view_urls.py
apps/common/urls/view_urls.py
+1
-0
views.py
apps/common/views.py
+26
-2
django.mo
apps/i18n/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/i18n/zh/LC_MESSAGES/django.po
+0
-0
settings.py
apps/jumpserver/settings.py
+3
-0
inventory.py
apps/ops/inventory.py
+1
-0
jumpserver.js
apps/static/js/jumpserver.js
+88
-0
pwstrength-bootstrap.js
apps/static/js/pwstrength-bootstrap.js
+0
-0
_base_create_update.html
apps/templates/_base_create_update.html
+1
-0
_footer.html
apps/templates/_footer.html
+1
-1
forms.py
apps/users/forms.py
+8
-7
user.py
apps/users/models/user.py
+17
-2
serializers.py
apps/users/serializers.py
+4
-1
signals_handler.py
apps/users/signals_handler.py
+9
-0
_user.html
apps/users/templates/users/_user.html
+1
-0
first_login.html
apps/users/templates/users/first_login.html
+3
-2
reset_password.html
apps/users/templates/users/reset_password.html
+43
-3
user_detail.html
apps/users/templates/users/user_detail.html
+4
-0
user_list.html
apps/users/templates/users/user_list.html
+4
-3
user_password_update.html
apps/users/templates/users/user_password_update.html
+41
-0
user_profile.html
apps/users/templates/users/user_profile.html
+7
-0
user_pubkey_update.html
apps/users/templates/users/user_pubkey_update.html
+6
-1
user_update.html
apps/users/templates/users/user_update.html
+45
-0
utils.py
apps/users/utils.py
+61
-0
login.py
apps/users/views/login.py
+15
-3
user.py
apps/users/views/user.py
+38
-2
config_example.py
config_example.py
+0
-5
No files found.
apps/__init__.py
View file @
e7a731fa
...
...
@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
#
__version__
=
"1.3.
1
"
__version__
=
"1.3.
2
"
apps/assets/api/node.py
View file @
e7a731fa
...
...
@@ -116,7 +116,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
def
get_object
(
self
):
pk
=
self
.
kwargs
.
get
(
'pk'
)
or
self
.
request
.
query_params
.
get
(
'id'
)
if
not
pk
:
node
=
No
de
.
root
()
node
=
No
ne
else
:
node
=
get_object_or_404
(
Node
,
pk
=
pk
)
return
node
...
...
@@ -126,7 +126,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
query_all
=
self
.
request
.
query_params
.
get
(
"all"
)
query_assets
=
self
.
request
.
query_params
.
get
(
'assets'
)
node
=
self
.
get_object
()
if
node
==
Node
.
root
():
if
node
is
None
:
node
=
Node
.
root
()
queryset
.
append
(
node
)
if
query_all
:
children
=
node
.
get_all_children
()
...
...
apps/assets/utils.py
View file @
e7a731fa
...
...
@@ -51,7 +51,6 @@ def test_gateway_connectability(gateway):
client
=
paramiko
.
SSHClient
()
client
.
set_missing_host_key_policy
(
paramiko
.
AutoAddPolicy
())
proxy
=
paramiko
.
SSHClient
()
proxy
.
load_host_keys
(
os
.
path
.
expanduser
(
'~/.ssh/known_hosts'
))
proxy
.
set_missing_host_key_policy
(
paramiko
.
AutoAddPolicy
())
try
:
...
...
apps/common/forms.py
View file @
e7a731fa
...
...
@@ -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'
)
)
apps/common/signals_handler.py
View file @
e7a731fa
...
...
@@ -34,7 +34,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
def
ldap_auth_on_changed
(
sender
,
enabled
=
True
,
**
kwargs
):
if
enabled
:
logger
.
debug
(
"Enable LDAP auth"
)
if
settings
.
AUTH_LDAP_BACKEND
not
in
settings
.
AUTH
_LDAP_BACKEND
:
if
settings
.
AUTH_LDAP_BACKEND
not
in
settings
.
AUTH
ENTICATION_BACKENDS
:
settings
.
AUTHENTICATION_BACKENDS
.
insert
(
0
,
settings
.
AUTH_LDAP_BACKEND
)
else
:
...
...
apps/common/templates/common/basic_setting.html
View file @
e7a731fa
...
...
@@ -23,6 +23,9 @@
<li>
<a
href=
"{% url 'settings:terminal-setting' %}"
class=
"text-center"
><i
class=
"fa fa-hdd-o"
></i>
{% trans 'Terminal setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:security-setting' %}"
class=
"text-center"
><i
class=
"fa fa-lock"
></i>
{% trans 'Security setting' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
...
...
apps/common/templates/common/email_setting.html
View file @
e7a731fa
...
...
@@ -23,6 +23,9 @@
<li>
<a
href=
"{% url 'settings:terminal-setting' %}"
class=
"text-center"
><i
class=
"fa fa-hdd-o"
></i>
{% trans 'Terminal setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:security-setting' %}"
class=
"text-center"
><i
class=
"fa fa-lock"
></i>
{% trans 'Security setting' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
...
...
apps/common/templates/common/ldap_setting.html
View file @
e7a731fa
...
...
@@ -23,6 +23,9 @@
<li>
<a
href=
"{% url 'settings:terminal-setting' %}"
class=
"text-center"
><i
class=
"fa fa-hdd-o"
></i>
{% trans 'Terminal setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:security-setting' %}"
class=
"text-center"
><i
class=
"fa fa-lock"
></i>
{% trans 'Security setting' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
...
...
apps/common/templates/common/security_setting.html
0 → 100644
View file @
e7a731fa
{% 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 %}
apps/common/templates/common/terminal_setting.html
View file @
e7a731fa
...
...
@@ -27,6 +27,9 @@
<a
href=
"{% url 'settings:terminal-setting' %}"
class=
"text-center"
><i
class=
"fa fa-hdd-o"
></i>
{% trans 'Terminal setting' %}
</a>
</li>
<li>
<a
href=
"{% url 'settings:security-setting' %}"
class=
"text-center"
><i
class=
"fa fa-lock"
></i>
{% trans 'Security setting' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
...
...
@@ -39,6 +42,7 @@
</div>
{% endif %}
{% csrf_token %}
<h3>
{% trans "Basic setting" %}
</h3>
{% for field in form %}
{% if not field.field|is_bool_field %}
...
...
@@ -60,6 +64,7 @@
{% endfor %}
<div
class=
"hr-line-dashed"
></div>
<h3>
{% trans "Command storage" %}
</h3>
<table
class=
"table table-hover "
id=
"task-history-list-table"
>
<thead>
...
...
apps/common/urls/view_urls.py
View file @
e7a731fa
...
...
@@ -11,4 +11,5 @@ urlpatterns = [
url
(
r'^email/$'
,
views
.
EmailSettingView
.
as_view
(),
name
=
'email-setting'
),
url
(
r'^ldap/$'
,
views
.
LDAPSettingView
.
as_view
(),
name
=
'ldap-setting'
),
url
(
r'^terminal/$'
,
views
.
TerminalSettingView
.
as_view
(),
name
=
'terminal-setting'
),
url
(
r'^security/$'
,
views
.
SecuritySettingView
.
as_view
(),
name
=
'security-setting'
),
]
apps/common/views.py
View file @
e7a731fa
...
...
@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from
django.conf
import
settings
from
.forms
import
EmailSettingForm
,
LDAPSettingForm
,
BasicSettingForm
,
\
TerminalSettingForm
TerminalSettingForm
,
SecuritySettingForm
from
.mixins
import
AdminUserRequiredMixin
from
.signals
import
ldap_auth_enable
...
...
@@ -82,7 +82,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
if
form
.
is_valid
():
form
.
save
()
if
"AUTH_LDAP"
in
form
.
cleaned_data
:
ldap_auth_enable
.
send
(
form
.
cleaned_data
[
"AUTH_LDAP"
])
ldap_auth_enable
.
send
(
sender
=
self
.
__class__
,
enabled
=
form
.
cleaned_data
[
"AUTH_LDAP"
])
msg
=
_
(
"Update setting successfully, please restart program"
)
messages
.
success
(
request
,
msg
)
return
redirect
(
'settings:ldap-setting'
)
...
...
@@ -122,3 +122,27 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
return
render
(
request
,
self
.
template_name
,
context
)
class
SecuritySettingView
(
AdminUserRequiredMixin
,
TemplateView
):
form_class
=
SecuritySettingForm
template_name
=
"common/security_setting.html"
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'app'
:
_
(
'Settings'
),
'action'
:
_
(
'Security setting'
),
'form'
:
self
.
form_class
(),
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
post
(
self
,
request
):
form
=
self
.
form_class
(
request
.
POST
)
if
form
.
is_valid
():
form
.
save
()
msg
=
_
(
"Update setting successfully, please restart program"
)
messages
.
success
(
request
,
msg
)
return
redirect
(
'settings:security-setting'
)
else
:
context
=
self
.
get_context_data
()
context
.
update
({
"form"
:
form
})
return
render
(
request
,
self
.
template_name
,
context
)
apps/i18n/zh/LC_MESSAGES/django.mo
View file @
e7a731fa
No preview for this file type
apps/i18n/zh/LC_MESSAGES/django.po
View file @
e7a731fa
This diff is collapsed.
Click to expand it.
apps/jumpserver/settings.py
View file @
e7a731fa
...
...
@@ -401,6 +401,9 @@ TERMINAL_REPLAY_STORAGE = {
},
}
DEFAULT_PASSWORD_MIN_LENGTH
=
6
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3
=
{
'horizontal_label_class'
:
'col-md-2'
,
...
...
apps/ops/inventory.py
View file @
e7a731fa
...
...
@@ -86,6 +86,7 @@ class JMSInventory(BaseInventory):
gateway
=
asset
.
domain
.
random_gateway
()
proxy_command_list
=
[
"ssh"
,
"-p"
,
str
(
gateway
.
port
),
"-o"
,
"StrictHostKeyChecking=no"
,
"{}@{}"
.
format
(
gateway
.
username
,
gateway
.
ip
),
"-W"
,
"
%
h:
%
p"
,
"-q"
,
]
...
...
apps/static/js/jumpserver.js
View file @
e7a731fa
...
...
@@ -609,3 +609,91 @@ function setUrlParam(url, name, value) {
}
return
url
}
// 校验密码-改变规则颜色
function
checkPasswordRules
(
password
,
minLength
)
{
if
(
wordMinLength
(
password
,
minLength
))
{
$
(
'#rule_SECURITY_PASSWORD_MIN_LENGTH'
).
css
(
'color'
,
'green'
)
}
else
{
$
(
'#rule_SECURITY_PASSWORD_MIN_LENGTH'
).
css
(
'color'
,
'#908a8a'
)
}
if
(
wordUpperCase
(
password
))
{
$
(
'#rule_SECURITY_PASSWORD_UPPER_CASE'
).
css
(
'color'
,
'green'
);
}
else
{
$
(
'#rule_SECURITY_PASSWORD_UPPER_CASE'
).
css
(
'color'
,
'#908a8a'
)
}
if
(
wordLowerCase
(
password
))
{
$
(
'#rule_SECURITY_PASSWORD_LOWER_CASE'
).
css
(
'color'
,
'green'
)
}
else
{
$
(
'#rule_SECURITY_PASSWORD_LOWER_CASE'
).
css
(
'color'
,
'#908a8a'
)
}
if
(
wordNumber
(
password
))
{
$
(
'#rule_SECURITY_PASSWORD_NUMBER'
).
css
(
'color'
,
'green'
)
}
else
{
$
(
'#rule_SECURITY_PASSWORD_NUMBER'
).
css
(
'color'
,
'#908a8a'
)
}
if
(
wordSpecialChar
(
password
))
{
$
(
'#rule_SECURITY_PASSWORD_SPECIAL_CHAR'
).
css
(
'color'
,
'green'
)
}
else
{
$
(
'#rule_SECURITY_PASSWORD_SPECIAL_CHAR'
).
css
(
'color'
,
'#908a8a'
)
}
}
// 最小长度
function
wordMinLength
(
word
,
minLength
)
{
//var minLength = {{ min_length }};
var
re
=
new
RegExp
(
"^(.{"
+
minLength
+
",})$"
);
return
word
.
match
(
re
)
}
// 大写字母
function
wordUpperCase
(
word
)
{
return
word
.
match
(
/
([
A-Z
]
+
)
/
)
}
// 小写字母
function
wordLowerCase
(
word
)
{
return
word
.
match
(
/
([
a-z
]
+
)
/
)
}
// 数字字符
function
wordNumber
(
word
)
{
return
word
.
match
(
/
([\d]
+
)
/
)
}
// 特殊字符
function
wordSpecialChar
(
word
)
{
return
word
.
match
(
/
[
`,~,!,@,#,
\$
,%,
\^
,&,
\*
,
\(
,
\)
,
\-
,_,=,
\+
,
\{
,
\}
,
\[
,
\]
,
\|
,
\\
,;,',:,",
\,
,
\.
,<,>,
\/
,
\?]
+/
)
}
// 显示弹窗密码规则
function
popoverPasswordRules
(
password_check_rules
,
$el
)
{
var
message
=
""
;
jQuery
.
each
(
password_check_rules
,
function
(
idx
,
rules
)
{
message
+=
"<li id="
+
rules
.
id
+
" style='list-style-type:none;'> <i class='fa fa-check-circle-o' style='margin-right:10px;' ></i>"
+
rules
.
label
+
"</li>"
;
});
//$('#id_password_rules').html(message);
$el
.
html
(
message
)
}
// 初始化弹窗popover
function
initPopover
(
$container
,
$progress
,
$idPassword
,
$el
,
password_check_rules
){
options
=
{};
// User Interface
options
.
ui
=
{
container
:
$container
,
viewports
:
{
progress
:
$progress
//errors: $('.popover-content')
},
showProgressbar
:
true
,
showVerdictsInsideProgressBar
:
true
};
$idPassword
.
pwstrength
(
options
);
popoverPasswordRules
(
password_check_rules
,
$el
);
}
apps/static/js/pwstrength-bootstrap.js
0 → 100755
View file @
e7a731fa
This diff is collapsed.
Click to expand it.
apps/templates/_base_create_update.html
View file @
e7a731fa
...
...
@@ -5,6 +5,7 @@
{% block custom_head_css_js %}
<link
href=
"{% static 'css/plugins/select2/select2.min.css' %}"
rel=
"stylesheet"
>
<script
src=
"{% static 'js/plugins/select2/select2.full.min.js' %}"
></script>
<script
type=
"text/javascript"
src=
"{% static 'js/pwstrength-bootstrap.js' %}"
></script>
{% block custom_head_css_js_create %} {% endblock %}
{% endblock %}
...
...
apps/templates/_footer.html
View file @
e7a731fa
<div
class=
"footer fixed"
>
<div
class=
"pull-right"
>
Version
<strong>
1.3.
1
-{% include '_build.html' %}
</strong>
GPLv2.
Version
<strong>
1.3.
2
-{% include '_build.html' %}
</strong>
GPLv2.
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div>
<div>
...
...
apps/users/forms.py
View file @
e7a731fa
...
...
@@ -16,13 +16,14 @@ class UserLoginForm(AuthenticationForm):
max_length
=
128
,
strip
=
False
)
def
confirm_login_allowed
(
self
,
user
):
if
not
user
.
is_staff
:
raise
forms
.
ValidationError
(
self
.
error_messages
[
'inactive'
],
code
=
'inactive'
,)
class
UserLoginCaptchaForm
(
AuthenticationForm
):
username
=
forms
.
CharField
(
label
=
_
(
'Username'
),
max_length
=
100
)
password
=
forms
.
CharField
(
label
=
_
(
'Password'
),
widget
=
forms
.
PasswordInput
,
max_length
=
128
,
strip
=
False
)
class
UserLoginCaptchaForm
(
UserLoginForm
):
captcha
=
CaptchaField
()
...
...
@@ -72,7 +73,7 @@ class UserCreateUpdateForm(forms.ModelForm):
'data-placeholder'
:
_
(
'Join user groups'
)
}
),
'otp_level'
:
forms
.
RadioSelect
()
'otp_level'
:
forms
.
RadioSelect
()
,
}
def
clean_public_key
(
self
):
...
...
apps/users/models/user.py
View file @
e7a731fa
...
...
@@ -14,6 +14,7 @@ from django.utils import timezone
from
django.shortcuts
import
reverse
from
common.utils
import
get_signer
,
date_expired_default
from
common.models
import
Setting
__all__
=
[
'User'
]
...
...
@@ -35,6 +36,12 @@ class User(AbstractUser):
(
1
,
_
(
'Enable'
)),
(
2
,
_
(
"Force enable"
)),
)
SOURCE_LOCAL
=
'local'
SOURCE_LDAP
=
'ldap'
SOURCE_CHOICES
=
(
(
SOURCE_LOCAL
,
'Local'
),
(
SOURCE_LDAP
,
'LDAP/AD'
),
)
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
username
=
models
.
CharField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Username'
)
...
...
@@ -82,6 +89,10 @@ class User(AbstractUser):
created_by
=
models
.
CharField
(
max_length
=
30
,
default
=
''
,
verbose_name
=
_
(
'Created by'
)
)
source
=
models
.
CharField
(
max_length
=
30
,
default
=
SOURCE_LOCAL
,
choices
=
SOURCE_CHOICES
,
verbose_name
=
_
(
'Source'
)
)
def
__str__
(
self
):
return
'{0.name}({0.username})'
.
format
(
self
)
...
...
@@ -248,14 +259,17 @@ class User(AbstractUser):
@property
def
otp_enabled
(
self
):
return
self
.
otp_level
>
0
return
self
.
otp_
force_enabled
or
self
.
otp_
level
>
0
@property
def
otp_force_enabled
(
self
):
mfa_setting
=
Setting
.
objects
.
filter
(
name
=
'SECURITY_MFA_AUTH'
)
.
first
()
if
mfa_setting
and
mfa_setting
.
cleaned_value
:
return
True
return
self
.
otp_level
==
2
def
enable_otp
(
self
):
if
not
self
.
otp_
force_enabled
:
if
not
self
.
otp_
level
==
2
:
self
.
otp_level
=
1
def
force_enable_otp
(
self
):
...
...
@@ -275,6 +289,7 @@ class User(AbstractUser):
'is_superuser'
:
self
.
is_superuser
,
'role'
:
self
.
get_role_display
(),
'groups'
:
[
group
.
name
for
group
in
self
.
groups
.
all
()],
'source'
:
self
.
get_source_display
(),
'wechat'
:
self
.
wechat
,
'phone'
:
self
.
phone
,
'otp_level'
:
self
.
otp_level
,
...
...
apps/users/serializers.py
View file @
e7a731fa
...
...
@@ -26,7 +26,10 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
super
(
UserSerializer
,
self
)
.
get_field_names
(
declared_fields
,
info
)
fields
.
extend
([
'groups_display'
,
'get_role_display'
,
'is_valid'
])
fields
.
extend
([
'groups_display'
,
'get_role_display'
,
'get_source_display'
,
'is_valid'
])
return
fields
@staticmethod
...
...
apps/users/signals_handler.py
View file @
e7a731fa
...
...
@@ -2,6 +2,7 @@
#
from
django.dispatch
import
receiver
from
django_auth_ldap.backend
import
populate_user
# from django.db.models.signals import post_save
from
common.utils
import
get_logger
...
...
@@ -28,3 +29,11 @@ def on_user_create(sender, user=None, **kwargs):
logger
.
info
(
" - Sending welcome mail ..."
.
format
(
user
.
name
))
if
user
.
email
:
send_user_created_mail
(
user
)
@receiver
(
populate_user
)
def
on_ldap_create_user
(
sender
,
user
,
ldap_user
,
**
kwargs
):
if
user
:
user
.
source
=
user
.
SOURCE_LDAP
user
.
save
()
apps/users/templates/users/_user.html
View file @
e7a731fa
...
...
@@ -48,6 +48,7 @@
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script
src=
"{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"
></script>
...
...
apps/users/templates/users/first_login.html
View file @
e7a731fa
...
...
@@ -138,8 +138,9 @@
}
}).
on
(
'click'
,
'#btn-reset-pubkey'
,
function
()
{
var
the_url
=
'{% url "users:user-pubkey-generate" %}'
;
window
.
open
(
the_url
,
"_blank"
)
})
window
.
open
(
the_url
,
"_blank"
);
$
(
'#fl_form'
).
submit
();
})
</script>
{% endblock %}
apps/users/templates/users/reset_password.html
View file @
e7a731fa
...
...
@@ -11,6 +11,7 @@
{% include '_head_css_js.html' %}
<link
href=
"{% static "
css
/
jumpserver
.
css
"
%}"
rel=
"stylesheet"
>
<script
src=
"{% static "
js
/
jumpserver
.
js
"
%}"
></script>
<script
type=
"text/javascript"
src=
"{% static 'js/pwstrength-bootstrap.js' %}"
></script>
</head>
...
...
@@ -49,10 +50,20 @@
<p
class=
"red-fonts"
>
{{ errors }}
</p>
{% endif %}
<div
class=
"form-group"
>
<input
type=
"password"
class=
"form-control"
name=
"password"
placeholder=
"{% trans 'Password' %}"
required=
""
>
<input
type=
"password"
id=
"id_new_password"
class=
"form-control"
name=
"password"
placeholder=
"{% trans 'Password' %}"
required=
""
>
{# 密码popover #}
<div
id=
"container"
>
<div
class=
"popover fade bottom in"
role=
"tooltip"
id=
"popover777"
style=
" display: none; width:260px;"
>
<div
class=
"arrow"
style=
"left: 50%;"
></div>
<h3
class=
"popover-title"
style=
"display: none;"
></h3>
<h4>
{% trans 'Your password must satisfy' %}
</h4><div
id=
"id_password_rules"
style=
"color: #908a8a; margin-left:20px; font-size:15px;"
></div>
<h4
style=
"margin-top: 10px;"
>
{% trans 'Password strength' %}
</h4><div
id=
"id_progress"
></div>
<div
class=
"popover-content"
></div>
</div>
</div>
</div>
<div
class=
"form-group"
>
<input
type=
"password"
class=
"form-control"
name=
"password-confirm"
placeholder=
"{% trans 'Password again' %}"
required=
""
>
<input
type=
"password"
id=
"id_confirm_password"
class=
"form-control"
name=
"password-confirm"
placeholder=
"{% trans 'Password again' %}"
required=
""
>
</div>
<button
type=
"submit"
class=
"btn btn-primary block full-width m-b"
>
{% trans "Setting" %}
</button>
...
...
@@ -79,4 +90,33 @@
</body>
</html>
<script>
$
(
document
).
ready
(
function
()
{
// 密码强度校验
var
el
=
$
(
'#id_password_rules'
),
idPassword
=
$
(
'#id_new_password'
),
idPopover
=
$
(
'#popover777'
),
container
=
$
(
'#container'
),
progress
=
$
(
'#id_progress'
),
password_check_rules
=
{{
password_check_rules
|
safe
}},
minLength
=
{{
min_length
}},
top
=
146
,
left
=
170
;
// 初始化popover
initPopover
(
container
,
progress
,
idPassword
,
el
,
password_check_rules
);
// 监听事件
idPassword
.
on
(
'focus'
,
function
()
{
idPopover
.
css
(
'top'
,
top
);
idPopover
.
css
(
'left'
,
left
);
idPopover
.
css
(
'display'
,
'block'
);
});
idPassword
.
on
(
'blur'
,
function
()
{
idPopover
.
css
(
'display'
,
'none'
);
});
idPassword
.
on
(
'keyup'
,
function
(){
var
password
=
idPassword
.
val
();
checkPasswordRules
(
password
,
minLength
);
})
})
</script>
apps/users/templates/users/user_detail.html
View file @
e7a731fa
...
...
@@ -99,6 +99,10 @@
{% endif %}
</b></td>
</tr>
<tr>
<td>
{% trans 'Source' %}:
</td>
<td><b>
{{ user_object.get_source_display }}
</b></td>
</tr>
<tr>
<td>
{% trans 'Date expired' %}:
</td>
<td><b>
{{ user_object.date_expired|date:"Y-m-j H:i:s" }}
</b></td>
...
...
apps/users/templates/users/user_list.html
View file @
e7a731fa
...
...
@@ -24,6 +24,7 @@
<th
class=
"text-center"
>
{% trans 'Username' %}
</th>
<th
class=
"text-center"
>
{% trans 'Role' %}
</th>
<th
class=
"text-center"
>
{% trans 'User group' %}
</th>
<th
class=
"text-center"
>
{% trans 'Source' %}
</th>
<th
class=
"text-center"
>
{% trans 'Active' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
</tr>
...
...
@@ -65,14 +66,14 @@ function initTable() {
var
innerHtml
=
cellData
.
length
>
20
?
cellData
.
substring
(
0
,
20
)
+
'...'
:
cellData
;
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
cellData
+
'">'
+
innerHtml
+
'</span>'
);
}},
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
)
{
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
!
cellData
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
}},
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
targets
:
7
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
update_btn
=
'<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
.
replace
(
'00000000-0000-0000-0000-000000000000'
,
cellData
);
var
del_btn
=
""
;
...
...
@@ -90,7 +91,7 @@ function initTable() {
ajax_url
:
'{% url "api-users:user-list" %}'
,
columns
:
[
{
data
:
"id"
},
{
data
:
"name"
},
{
data
:
"username"
},
{
data
:
"get_role_display"
},
{
data
:
"groups_display"
},
{
data
:
"is_valid"
},
{
data
:
"id"
}
{
data
:
"groups_display"
},
{
data
:
"
get_source_display"
},
{
data
:
"
is_valid"
},
{
data
:
"id"
}
],
op_html
:
$
(
'#actions'
).
html
()
};
...
...
apps/users/templates/users/user_password_update.html
View file @
e7a731fa
...
...
@@ -7,6 +7,8 @@
<link
href=
"{% static "
css
/
plugins
/
cropper
/
cropper
.
min
.
css
"
%}"
rel=
"stylesheet"
>
<link
href=
"{% static "
css
/
plugins
/
sweetalert
/
sweetalert
.
css
"
%}"
rel=
"stylesheet"
>
<script
src=
"{% static "
js
/
plugins
/
sweetalert
/
sweetalert
.
min
.
js
"
%}"
></script>
<script
type=
"text/javascript"
src=
"{% static 'js/pwstrength-bootstrap.js' %}"
></script>
<script
src=
"{% static "
js
/
jumpserver
.
js
"
%}"
></script>
<style>
.crop
{
...
...
@@ -50,6 +52,16 @@
{% csrf_token %}
{% bootstrap_field form.old_password layout="horizontal" %}
{% bootstrap_field form.new_password layout="horizontal" %}
{# 密码popover #}
<div
id=
"container"
>
<div
class=
"popover fade bottom in"
role=
"tooltip"
id=
"popover777"
style=
" display: none; width:260px;"
>
<div
class=
"arrow"
style=
"left: 50%;"
></div>
<h3
class=
"popover-title"
style=
"display: none;"
></h3>
<h4>
{% trans 'Your password must satisfy' %}
</h4><div
id=
"id_password_rules"
style=
"color: #908a8a; margin-left:20px; font-size:15px;"
></div>
<h4
style=
"margin-top: 10px;"
>
{% trans 'Password strength' %}
</h4><div
id=
"id_progress"
></div>
<div
class=
"popover-content"
></div>
</div>
</div>
{% bootstrap_field form.confirm_password layout="horizontal" %}
<div
class=
"hr-line-dashed"
></div>
...
...
@@ -71,5 +83,34 @@
{% block custom_foot_js %}
<script
src=
"{% static 'js/plugins/cropper/cropper.min.js' %}"
></script>
<script>
$
(
document
).
ready
(
function
()
{
var
el
=
$
(
'#id_password_rules'
),
idPassword
=
$
(
'#id_new_password'
),
idPopover
=
$
(
'#popover777'
),
container
=
$
(
'#container'
),
progress
=
$
(
'#id_progress'
),
password_check_rules
=
{{
password_check_rules
|
safe
}},
minLength
=
{{
min_length
}},
top
=
idPassword
.
offset
().
top
-
$
(
'.navbar'
).
outerHeight
(
true
)
-
$
(
'.page-heading'
).
outerHeight
(
true
)
-
10
+
34
,
left
=
377
;
// 初始化popover
initPopover
(
container
,
progress
,
idPassword
,
el
,
password_check_rules
);
// 监听事件
idPassword
.
on
(
'focus'
,
function
()
{
idPopover
.
css
(
'top'
,
top
);
idPopover
.
css
(
'left'
,
left
);
idPopover
.
css
(
'display'
,
'block'
);
});
idPassword
.
on
(
'blur'
,
function
()
{
idPopover
.
css
(
'display'
,
'none'
);
});
idPassword
.
on
(
'keyup'
,
function
(){
var
password
=
idPassword
.
val
();
checkPasswordRules
(
password
,
minLength
);
});
})
</script>
{% endblock %}
apps/users/templates/users/user_profile.html
View file @
e7a731fa
...
...
@@ -91,8 +91,15 @@
{% else %}
{% trans 'Disable' %}
{% endif %}
{% if mfa_setting %}
( {% trans 'Administrator Settings force MFA login' %} )
{% endif %}
</td>
</tr>
<tr>
<td
class=
"text-navy"
>
{% trans 'Source' %}
</td>
<td>
{{ user.get_source_display }}
</td>
</tr>
<tr>
<td
class=
"text-navy"
>
{% trans 'Date joined' %}
</td>
<td>
{{ user.date_joined|date:"Y-m-d H:i:s" }}
</td>
...
...
apps/users/templates/users/user_pubkey_update.html
View file @
e7a731fa
...
...
@@ -67,7 +67,7 @@
<div
class=
"form-group"
>
<label
class=
"control-label col-sm-2 col-lg-2"
style=
"padding-top: 0"
>
{% trans 'Or reset by server' %}
</label>
<div
class=
" col-sm-9 col-lg-9 "
>
<a
href=
"{% url 'users:user-pubkey-generate' %}"
>
{% trans 'Reset' %}
</a>
<a
id=
"reset_pubkey"
href=
"{% url 'users:user-pubkey-generate' %}"
>
{% trans 'Reset' %}
</a>
</div>
</div>
<div
class=
"hr-line-dashed"
></div>
...
...
@@ -89,5 +89,10 @@
{% block custom_foot_js %}
<script
src=
"{% static 'js/plugins/cropper/cropper.min.js' %}"
></script>
<script>
$
(
document
).
ready
(
function
()
{
}).
on
(
'click'
,
'#reset_pubkey'
,
function
()
{
var
message
=
"{% trans 'The new public key has been set successfully, Please download the corresponding private key.' %}"
;
toastr
.
success
(
message
)
})
</script>
{% endblock %}
apps/users/templates/users/user_update.html
View file @
e7a731fa
...
...
@@ -4,5 +4,50 @@
{% block user_template_title %}{% trans "Update user" %}{% endblock %}
{% block password %}
{% bootstrap_field form.password layout="horizontal" %}
{# 密码popover #}
<div
id=
"container"
>
<div
class=
"popover fade bottom in"
role=
"tooltip"
id=
"popover777"
style=
" display: none; width:260px;"
>
<div
class=
"arrow"
style=
"left: 50%;"
></div>
<h3
class=
"popover-title"
style=
"display: none;"
></h3>
<h4>
{% trans 'Your password must satisfy' %}
</h4><div
id=
"id_password_rules"
style=
"color: #908a8a; margin-left:20px; font-size:15px;"
></div>
<h4
style=
"margin-top: 10px;"
>
{% trans 'Password strength' %}
</h4><div
id=
"id_progress"
></div>
<div
class=
"popover-content"
></div>
</div>
</div>
{% bootstrap_field form.public_key layout="horizontal" %}
{% endblock %}
{% block custom_foot_js %}
{{ block.super }}
<script>
$
(
document
).
ready
(
function
(){
var
el
=
$
(
'#id_password_rules'
),
idPassword
=
$
(
'#id_password'
),
idPopover
=
$
(
'#popover777'
),
container
=
$
(
'#container'
),
progress
=
$
(
'#id_progress'
),
password_check_rules
=
{{
password_check_rules
|
safe
}},
minLength
=
{{
min_length
}},
top
=
idPassword
.
offset
().
top
-
$
(
'.navbar'
).
outerHeight
(
true
)
-
$
(
'.page-heading'
).
outerHeight
(
true
)
-
10
+
34
,
left
=
377
;
// 初始化popover
initPopover
(
container
,
progress
,
idPassword
,
el
,
password_check_rules
);
// 监听事件
idPassword
.
on
(
'focus'
,
function
()
{
idPopover
.
css
(
'top'
,
top
);
idPopover
.
css
(
'left'
,
left
);
idPopover
.
css
(
'display'
,
'block'
);
});
idPassword
.
on
(
'blur'
,
function
()
{
idPopover
.
css
(
'display'
,
'none'
);
});
idPassword
.
on
(
'keyup'
,
function
(){
var
password
=
idPassword
.
val
();
checkPasswordRules
(
password
,
minLength
);
});
})
</script>
{% endblock %}
apps/users/utils.py
View file @
e7a731fa
...
...
@@ -2,6 +2,7 @@
#
from
__future__
import
unicode_literals
import
os
import
re
import
pyotp
import
base64
import
logging
...
...
@@ -18,8 +19,11 @@ from django.core.cache import cache
from
common.tasks
import
send_mail_async
from
common.utils
import
reverse
,
get_object_or_none
from
common.models
import
Setting
from
common.forms
import
SecuritySettingForm
from
.models
import
User
,
LoginLog
logger
=
logging
.
getLogger
(
'jumpserver'
)
...
...
@@ -271,3 +275,60 @@ def generate_otp_uri(request, issuer="Jumpserver"):
def
check_otp_code
(
otp_secret_key
,
otp_code
):
totp
=
pyotp
.
TOTP
(
otp_secret_key
)
return
totp
.
verify
(
otp_code
)
def
get_password_check_rules
():
check_rules
=
[]
min_length
=
settings
.
DEFAULT_PASSWORD_MIN_LENGTH
min_name
=
'SECURITY_PASSWORD_MIN_LENGTH'
base_filed
=
SecuritySettingForm
.
base_fields
password_setting
=
Setting
.
objects
.
filter
(
name__startswith
=
'SECURITY_PASSWORD'
)
if
not
password_setting
:
# 用户还没有设置过密码校验规则
label
=
base_filed
.
get
(
min_name
)
.
label
label
+=
' '
+
str
(
min_length
)
+
_
(
'Bit'
)
id
=
'rule_'
+
min_name
rules
=
{
'id'
:
id
,
'label'
:
label
}
check_rules
.
append
(
rules
)
for
setting
in
password_setting
:
if
setting
.
cleaned_value
:
id
=
'rule_'
+
setting
.
name
label
=
base_filed
.
get
(
setting
.
name
)
.
label
if
setting
.
name
==
min_name
:
label
+=
str
(
setting
.
cleaned_value
)
+
_
(
'Bit'
)
min_length
=
setting
.
cleaned_value
rules
=
{
'id'
:
id
,
'label'
:
label
}
check_rules
.
append
(
rules
)
return
check_rules
,
min_length
def
check_password_rules
(
password
):
min_field_name
=
'SECURITY_PASSWORD_MIN_LENGTH'
upper_field_name
=
'SECURITY_PASSWORD_UPPER_CASE'
lower_field_name
=
'SECURITY_PASSWORD_LOWER_CASE'
number_field_name
=
'SECURITY_PASSWORD_NUMBER'
special_field_name
=
'SECURITY_PASSWORD_SPECIAL_CHAR'
min_length_setting
=
Setting
.
objects
.
filter
(
name
=
min_field_name
)
.
first
()
min_length
=
min_length_setting
.
value
if
min_length_setting
else
settings
.
DEFAULT_PASSWORD_MIN_LENGTH
password_setting
=
Setting
.
objects
.
filter
(
name__startswith
=
'SECURITY_PASSWORD'
)
if
not
password_setting
:
pattern
=
r"^.{"
+
str
(
min_length
)
+
",}$"
else
:
pattern
=
r"^"
for
setting
in
password_setting
:
if
setting
.
cleaned_value
and
setting
.
name
==
upper_field_name
:
pattern
+=
'(?=.*[A-Z])'
elif
setting
.
cleaned_value
and
setting
.
name
==
lower_field_name
:
pattern
+=
'(?=.*[a-z])'
elif
setting
.
cleaned_value
and
setting
.
name
==
number_field_name
:
pattern
+=
'(?=.*
\
d)'
elif
setting
.
cleaned_value
and
setting
.
name
==
special_field_name
:
pattern
+=
'(?=.*[`~!@#
\
$
%
\
^&
\
*
\
(
\
)-=_
\
+
\
[
\
]
\
{
\
}
\
|;:
\'
",
\
.<>
\
/
\
?])'
pattern
+=
'[a-zA-Z
\
d`~!@#
\
$
%
\
^&
\
*
\
(
\
)-=_
\
+
\
[
\
]
\
{
\
}
\
|;:
\'
",
\
.<>
\
/
\
?]'
match_obj
=
re
.
match
(
pattern
,
password
)
return
bool
(
match_obj
)
apps/users/views/login.py
View file @
e7a731fa
...
...
@@ -23,9 +23,10 @@ from django.conf import settings
from
common.utils
import
get_object_or_none
from
common.mixins
import
DatetimeSearchMixin
,
AdminUserRequiredMixin
from
common.models
import
Setting
from
..models
import
User
,
LoginLog
from
..utils
import
send_reset_password_mail
,
check_otp_code
,
get_login_ip
,
redirect_user_first_login_or_index
,
\
get_user_or_tmp_user
,
set_tmp_user_to_cache
get_user_or_tmp_user
,
set_tmp_user_to_cache
,
get_password_check_rules
,
check_password_rules
from
..tasks
import
write_login_log_async
from
..
import
forms
...
...
@@ -82,10 +83,10 @@ class UserLoginView(FormView):
user
=
get_user_or_tmp_user
(
self
.
request
)
if
user
.
otp_enabled
and
user
.
otp_secret_key
:
# 1,2 & T
# 1,2
,mfa_setting
& T
return
reverse
(
'users:login-otp'
)
elif
user
.
otp_enabled
and
not
user
.
otp_secret_key
:
# 1,2 & F
# 1,2
,mfa_setting
& F
return
reverse
(
'users:user-otp-enable-authentication'
)
elif
not
user
.
otp_enabled
:
# 0 & T,F
...
...
@@ -211,6 +212,10 @@ class UserResetPasswordView(TemplateView):
token
=
request
.
GET
.
get
(
'token'
)
user
=
User
.
validate_reset_token
(
token
)
check_rules
,
min_length
=
get_password_check_rules
()
password_rules
=
{
'password_check_rules'
:
check_rules
,
'min_length'
:
min_length
}
kwargs
.
update
(
password_rules
)
if
not
user
:
kwargs
.
update
({
'errors'
:
_
(
'Token invalid or expired'
)})
return
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
...
...
@@ -227,6 +232,13 @@ class UserResetPasswordView(TemplateView):
if
not
user
:
return
self
.
get
(
request
,
errors
=
_
(
'Token invalid or expired'
))
is_ok
=
check_password_rules
(
password
)
if
not
is_ok
:
return
self
.
get
(
request
,
errors
=
_
(
'* Your password does not meet the requirements'
)
)
user
.
reset_password
(
password
)
return
HttpResponseRedirect
(
reverse
(
'users:reset-password-success'
))
...
...
apps/users/views/user.py
View file @
e7a731fa
...
...
@@ -33,9 +33,10 @@ from django.contrib.auth import logout as auth_logout
from
common.const
import
create_success_msg
,
update_success_msg
from
common.mixins
import
JSONResponseMixin
from
common.utils
import
get_logger
,
get_object_or_none
,
is_uuid
,
ssh_key_gen
from
common.models
import
Setting
from
..
import
forms
from
..models
import
User
,
UserGroup
from
..utils
import
AdminUserRequiredMixin
,
generate_otp_uri
,
check_otp_code
,
get_user_or_tmp_user
from
..utils
import
AdminUserRequiredMixin
,
generate_otp_uri
,
check_otp_code
,
get_user_or_tmp_user
,
get_password_check_rules
,
check_password_rules
from
..signals
import
post_user_create
from
..tasks
import
write_login_log_async
...
...
@@ -96,10 +97,29 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
success_message
=
update_success_msg
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'app'
:
_
(
'Users'
),
'action'
:
_
(
'Update user'
)}
check_rules
,
min_length
=
get_password_check_rules
()
context
=
{
'app'
:
_
(
'Users'
),
'action'
:
_
(
'Update user'
),
'password_check_rules'
:
check_rules
,
'min_length'
:
min_length
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
form_valid
(
self
,
form
):
password
=
form
.
cleaned_data
.
get
(
'password'
)
if
not
password
:
return
super
()
.
form_valid
(
form
)
is_ok
=
check_password_rules
(
password
)
if
not
is_ok
:
form
.
add_error
(
"password"
,
_
(
"* Your password does not meet the requirements"
)
)
return
self
.
form_invalid
(
form
)
return
super
()
.
form_valid
(
form
)
class
UserBulkUpdateView
(
AdminUserRequiredMixin
,
TemplateView
):
model
=
User
...
...
@@ -318,8 +338,10 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
template_name
=
'users/user_profile.html'
def
get_context_data
(
self
,
**
kwargs
):
mfa_setting
=
Setting
.
objects
.
filter
(
name
=
'SECURITY_MFA_AUTH'
)
.
first
()
context
=
{
'action'
:
_
(
'Profile'
),
'mfa_setting'
:
mfa_setting
.
cleaned_value
if
mfa_setting
else
False
,
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
@@ -353,9 +375,12 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
return
self
.
request
.
user
def
get_context_data
(
self
,
**
kwargs
):
check_rules
,
min_length
=
get_password_check_rules
()
context
=
{
'app'
:
_
(
'Users'
),
'action'
:
_
(
'Password update'
),
'password_check_rules'
:
check_rules
,
'min_length'
:
min_length
,
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
@@ -364,6 +389,17 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
auth_logout
(
self
.
request
)
return
super
()
.
get_success_url
()
def
form_valid
(
self
,
form
):
password
=
form
.
cleaned_data
.
get
(
'new_password'
)
is_ok
=
check_password_rules
(
password
)
if
not
is_ok
:
form
.
add_error
(
"new_password"
,
_
(
"* Your password does not meet the requirements"
)
)
return
self
.
form_invalid
(
form
)
return
super
()
.
form_valid
(
form
)
class
UserPublicKeyUpdateView
(
LoginRequiredMixin
,
UpdateView
):
template_name
=
'users/user_pubkey_update.html'
...
...
config_example.py
View file @
e7a731fa
...
...
@@ -51,11 +51,6 @@ class Config:
REDIS_HOST
=
'127.0.0.1'
REDIS_PORT
=
6379
REDIS_PASSWORD
=
''
BROKER_URL
=
'redis://
%(password)
s
%(host)
s:
%(port)
s/3'
%
{
'password'
:
REDIS_PASSWORD
,
'host'
:
REDIS_HOST
,
'port'
:
REDIS_PORT
,
}
def
__init__
(
self
):
pass
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment