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
0a2ff83c
Commit
0a2ff83c
authored
6 years ago
by
ibuler
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/dev' into dev
parents
7276bd0b
73f9f546
master
auditor_jym
audits
dev
dev_beta
dev_beta_db
gengmei
node_service
password
rbac
restrict_access
test
v52
wph
1.5.2
1.5.1
1.5.0
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.3
v1.4.10
v1.4.7
v1.4.4
No related merge requests found
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
40 changed files
with
635 additions
and
53 deletions
+635
-53
__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
utils.py
apps/perms/utils.py
+48
-11
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
+2
-2
forms.py
apps/users/forms.py
+8
-7
group.py
apps/users/models/group.py
+1
-3
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
clean_duplicate_user_groups.py
utils/clean_duplicate_user_groups.py
+6
-1
No files found.
apps/__init__.py
View file @
0a2ff83c
...
...
@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
#
__version__
=
"1.3.
1
"
__version__
=
"1.3.
2
"
This diff is collapsed.
Click to expand it.
apps/assets/api/node.py
View file @
0a2ff83c
...
...
@@ -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
()
...
...
This diff is collapsed.
Click to expand it.
apps/assets/utils.py
View file @
0a2ff83c
...
...
@@ -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
:
...
...
This diff is collapsed.
Click to expand it.
apps/common/forms.py
View file @
0a2ff83c
...
...
@@ -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'
)
)
This diff is collapsed.
Click to expand it.
apps/common/signals_handler.py
View file @
0a2ff83c
...
...
@@ -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
:
...
...
This diff is collapsed.
Click to expand it.
apps/common/templates/common/basic_setting.html
View file @
0a2ff83c
...
...
@@ -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"
>
...
...
This diff is collapsed.
Click to expand it.
apps/common/templates/common/email_setting.html
View file @
0a2ff83c
...
...
@@ -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"
>
...
...
This diff is collapsed.
Click to expand it.
apps/common/templates/common/ldap_setting.html
View file @
0a2ff83c
...
...
@@ -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"
>
...
...
This diff is collapsed.
Click to expand it.
apps/common/templates/common/security_setting.html
0 → 100644
View file @
0a2ff83c
{% 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 %}
This diff is collapsed.
Click to expand it.
apps/common/templates/common/terminal_setting.html
View file @
0a2ff83c
...
...
@@ -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>
...
...
This diff is collapsed.
Click to expand it.
apps/common/urls/view_urls.py
View file @
0a2ff83c
...
...
@@ -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'
),
]
This diff is collapsed.
Click to expand it.
apps/common/views.py
View file @
0a2ff83c
...
...
@@ -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
)
This diff is collapsed.
Click to expand it.
apps/i18n/zh/LC_MESSAGES/django.mo
View file @
0a2ff83c
No preview for this file type
This diff is collapsed.
Click to expand it.
apps/i18n/zh/LC_MESSAGES/django.po
View file @
0a2ff83c
This diff is collapsed.
Click to expand it.
apps/jumpserver/settings.py
View file @
0a2ff83c
...
...
@@ -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'
,
...
...
This diff is collapsed.
Click to expand it.
apps/ops/inventory.py
View file @
0a2ff83c
...
...
@@ -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"
,
]
...
...
This diff is collapsed.
Click to expand it.
apps/perms/utils.py
View file @
0a2ff83c
...
...
@@ -11,10 +11,48 @@ from .hands import Node
logger
=
get_logger
(
__file__
)
class
Tree
:
def
__init__
(
self
):
self
.
__all_nodes
=
list
(
Node
.
objects
.
all
()
.
prefetch_related
(
'assets'
))
self
.
__node_asset_map
=
defaultdict
(
set
)
self
.
nodes
=
defaultdict
(
dict
)
self
.
root
=
Node
.
root
()
self
.
init_node_asset_map
()
def
init_node_asset_map
(
self
):
for
node
in
self
.
__all_nodes
:
assets
=
node
.
get_assets
()
.
values_list
(
'id'
,
flat
=
True
)
for
asset
in
assets
:
self
.
__node_asset_map
[
str
(
asset
)]
.
add
(
node
)
def
add_asset
(
self
,
asset
,
system_users
):
nodes
=
self
.
__node_asset_map
.
get
(
str
(
asset
.
id
),
[])
self
.
add_nodes
(
nodes
)
for
node
in
nodes
:
self
.
nodes
[
node
][
asset
]
.
update
(
system_users
)
def
add_node
(
self
,
node
):
if
node
in
self
.
nodes
:
return
else
:
self
.
nodes
[
node
]
=
defaultdict
(
set
)
if
node
.
key
==
self
.
root
.
key
:
return
parent_key
=
':'
.
join
(
node
.
key
.
split
(
':'
)[:
-
1
])
for
n
in
self
.
__all_nodes
:
if
n
.
key
==
parent_key
:
self
.
add_node
(
n
)
break
def
add_nodes
(
self
,
nodes
):
for
node
in
nodes
:
self
.
add_node
(
node
)
def
get_user_permissions
(
user
,
include_group
=
True
):
if
include_group
:
groups
=
user
.
groups
.
all
()
arg
=
Q
(
users
=
user
)
|
Q
(
user_groups
=
groups
)
arg
=
Q
(
users
=
user
)
|
Q
(
user_groups
__in
=
groups
)
else
:
arg
=
Q
(
users
=
user
)
return
AssetPermission
.
objects
.
all
()
.
valid
()
.
filter
(
arg
)
...
...
@@ -29,7 +67,7 @@ def get_user_group_permissions(user_group):
def
get_asset_permissions
(
asset
,
include_node
=
True
):
if
include_node
:
nodes
=
asset
.
get_all_nodes
(
flat
=
True
)
arg
=
Q
(
assets
=
asset
)
|
Q
(
nodes
=
nodes
)
arg
=
Q
(
assets
=
asset
)
|
Q
(
nodes
__in
=
nodes
)
else
:
arg
=
Q
(
assets
=
asset
)
return
AssetPermission
.
objects
.
all
()
.
valid
()
.
filter
(
arg
)
...
...
@@ -57,6 +95,7 @@ class AssetPermissionUtil:
def
__init__
(
self
,
obj
):
self
.
object
=
obj
self
.
_permissions
=
None
self
.
_assets
=
None
@property
def
permissions
(
self
):
...
...
@@ -93,6 +132,8 @@ class AssetPermissionUtil:
return
assets
def
get_assets
(
self
):
if
self
.
_assets
:
return
self
.
_assets
assets
=
self
.
get_assets_direct
()
nodes
=
self
.
get_nodes_direct
()
for
node
,
system_users
in
nodes
.
items
():
...
...
@@ -101,7 +142,8 @@ class AssetPermissionUtil:
if
isinstance
(
asset
,
Node
):
print
(
_assets
)
assets
[
asset
]
.
update
(
system_users
)
return
assets
self
.
_assets
=
assets
return
self
.
_assets
def
get_nodes_with_assets
(
self
):
"""
...
...
@@ -110,14 +152,9 @@ class AssetPermissionUtil:
:return:
"""
assets
=
self
.
get_assets
()
nodes
=
defaultdict
(
dict
)
tree
=
Tree
(
)
for
asset
,
system_users
in
assets
.
items
():
_nodes
=
asset
.
nodes
.
all
()
for
node
in
_nodes
:
if
asset
in
nodes
[
node
]:
nodes
[
node
][
asset
]
.
update
(
system_users
)
else
:
nodes
[
node
][
asset
]
=
system_users
return
nodes
tree
.
add_asset
(
asset
,
system_users
)
return
tree
.
nodes
This diff is collapsed.
Click to expand it.
apps/static/js/jumpserver.js
View file @
0a2ff83c
...
...
@@ -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
);
}
This diff is collapsed.
Click to expand it.
apps/static/js/pwstrength-bootstrap.js
0 → 100755
View file @
0a2ff83c
This diff is collapsed.
Click to expand it.
apps/templates/_base_create_update.html
View file @
0a2ff83c
...
...
@@ -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 %}
...
...
This diff is collapsed.
Click to expand it.
apps/templates/_footer.html
View file @
0a2ff83c
<div
class=
"footer fixed"
>
<div
class=
"pull-right"
>
Version
<strong>
1.3.
1
-{% include '_build.html' %}
</strong>
GPLv2.
<
img
style=
"display: none"
src=
"http://www.jumpserver.org/img/evaluate_avatar1.jpg"
>
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>
<strong>
Copyright
</strong>
北京堆栈科技有限公司
©
2014-2018
...
...
This diff is collapsed.
Click to expand it.
apps/users/forms.py
View file @
0a2ff83c
...
...
@@ -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
):
...
...
This diff is collapsed.
Click to expand it.
apps/users/models/group.py
View file @
0a2ff83c
...
...
@@ -4,12 +4,10 @@ import uuid
from
django.db
import
models
,
IntegrityError
from
django.utils.translation
import
ugettext_lazy
as
_
from
common.mixins
import
NoDeleteModelMixin
__all__
=
[
'UserGroup'
]
class
UserGroup
(
NoDeleteModelMixin
):
class
UserGroup
(
models
.
Model
):
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
name
=
models
.
CharField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Name'
))
comment
=
models
.
TextField
(
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
...
...
This diff is collapsed.
Click to expand it.
apps/users/models/user.py
View file @
0a2ff83c
...
...
@@ -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
,
...
...
This diff is collapsed.
Click to expand it.
apps/users/serializers.py
View file @
0a2ff83c
...
...
@@ -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
...
...
This diff is collapsed.
Click to expand it.
apps/users/signals_handler.py
View file @
0a2ff83c
...
...
@@ -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
()
This diff is collapsed.
Click to expand it.
apps/users/templates/users/_user.html
View file @
0a2ff83c
...
...
@@ -48,6 +48,7 @@
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script
src=
"{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"
></script>
...
...
This diff is collapsed.
Click to expand it.
apps/users/templates/users/first_login.html
View file @
0a2ff83c
...
...
@@ -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 %}
This diff is collapsed.
Click to expand it.
apps/users/templates/users/reset_password.html
View file @
0a2ff83c
...
...
@@ -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>
This diff is collapsed.
Click to expand it.
apps/users/templates/users/user_detail.html
View file @
0a2ff83c
...
...
@@ -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>
...
...
This diff is collapsed.
Click to expand it.
apps/users/templates/users/user_list.html
View file @
0a2ff83c
...
...
@@ -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
()
};
...
...
This diff is collapsed.
Click to expand it.
apps/users/templates/users/user_password_update.html
View file @
0a2ff83c
...
...
@@ -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 %}
This diff is collapsed.
Click to expand it.
apps/users/templates/users/user_profile.html
View file @
0a2ff83c
...
...
@@ -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>
...
...
This diff is collapsed.
Click to expand it.
apps/users/templates/users/user_pubkey_update.html
View file @
0a2ff83c
...
...
@@ -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 %}
This diff is collapsed.
Click to expand it.
apps/users/templates/users/user_update.html
View file @
0a2ff83c
...
...
@@ -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 %}
This diff is collapsed.
Click to expand it.
apps/users/utils.py
View file @
0a2ff83c
...
...
@@ -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
)
This diff is collapsed.
Click to expand it.
apps/users/views/login.py
View file @
0a2ff83c
...
...
@@ -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'
))
...
...
This diff is collapsed.
Click to expand it.
apps/users/views/user.py
View file @
0a2ff83c
...
...
@@ -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'
...
...
This diff is collapsed.
Click to expand it.
config_example.py
View file @
0a2ff83c
...
...
@@ -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
...
...
This diff is collapsed.
Click to expand it.
utils/clean_duplicate_user_groups.py
View file @
0a2ff83c
...
...
@@ -17,9 +17,14 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
django
.
setup
()
from
users.models
import
UserGroup
from
django.core.exceptions
import
FieldError
def
clean_group
(
interactive
=
True
):
try
:
UserGroup
.
objects
.
all
()
.
filter
(
is_discard
=
True
)
.
delete
()
except
FieldError
:
pass
groups
=
UserGroup
.
objects
.
all
()
groups_name_list
=
groups
.
values_list
(
'name'
,
flat
=
True
)
groups_with_info
=
groups
.
annotate
(
Count
(
'users'
))
\
...
...
@@ -50,7 +55,7 @@ def clean_group(interactive=True):
"Delete user group <{}>, create at {}? ([y]/n)"
.
format
(
name
,
group
.
date_created
)
)
if
confirm
.
lower
()
==
"y"
:
if
confirm
.
lower
()
in
[
"y"
,
""
]
:
confirm
=
True
break
elif
confirm
.
lower
()
==
"n"
:
...
...
This diff is collapsed.
Click to expand it.
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