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