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
2ecfecb0
Commit
2ecfecb0
authored
Jul 25, 2018
by
ibuler
Browse files
Options
Browse Files
Download
Plain Diff
[Update] Merge with dev
parents
2abb9efe
e41add61
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
364 additions
and
103 deletions
+364
-103
README.md
README.md
+5
-5
__init__.py
apps/__init__.py
+1
-1
asset.py
apps/assets/models/asset.py
+1
-1
system_user.py
apps/assets/serializers/system_user.py
+1
-1
gateway_create_update.html
apps/assets/templates/assets/gateway_create_update.html
+12
-3
system_user_detail.html
apps/assets/templates/assets/system_user_detail.html
+8
-9
views_urls.py
apps/assets/urls/views_urls.py
+0
-1
mixins.py
apps/common/mixins.py
+4
-1
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
+1
-0
views.py
apps/jumpserver/views.py
+2
-2
models.py
apps/orgs/models.py
+4
-2
api.py
apps/perms/api.py
+3
-8
views_urls.py
apps/perms/urls/views_urls.py
+7
-9
_footer.html
apps/templates/_footer.html
+1
-1
api.py
apps/terminal/api.py
+2
-0
command_list.html
apps/terminal/templates/terminal/command_list.html
+39
-14
command_report.html
apps/terminal/templates/terminal/command_report.html
+104
-0
views_urls.py
apps/terminal/urls/views_urls.py
+1
-0
command.py
apps/terminal/views/command.py
+43
-5
api.py
apps/users/api.py
+21
-3
user_detail.html
apps/users/templates/users/user_detail.html
+44
-2
user_list.html
apps/users/templates/users/user_list.html
+1
-1
api_urls.py
apps/users/urls/api_urls.py
+2
-0
views_urls.py
apps/users/urls/views_urls.py
+20
-20
utils.py
apps/users/utils.py
+20
-5
login.py
apps/users/views/login.py
+5
-3
user.py
apps/users/views/user.py
+8
-2
step_by_step.rst
docs/step_by_step.rst
+1
-1
jms
jms
+1
-1
requirements.txt
requirements/requirements.txt
+1
-1
make_migrations.sh
utils/make_migrations.sh
+1
-1
No files found.
README.md
View file @
2ecfecb0
...
...
@@ -19,25 +19,25 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
----
### 功能
!
[
Jumpserver功能
](
https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver13.jpg
"Jumpserver功能"
)
### 开始使用
快速开始文档
[
Docker安装
](
http://docs.jumpserver.org/zh/
latest/quickstart
.html
)
快速开始文档
[
Docker安装
](
http://docs.jumpserver.org/zh/
docs/dockerinstall
.html
)
一步一步安装文档
[
详细部署
](
http://docs.jumpserver.org/zh/
latest
/step_by_step.html
)
一步一步安装文档
[
详细部署
](
http://docs.jumpserver.org/zh/
docs
/step_by_step.html
)
也可以查看我们完整文档包括了使用和开发
[
文档
](
http://docs.jumpserver.org
)
### Demo 和 截图
### Demo 和 截图
我们提供了DEMO和截图可以让你快速了解Jumpserver
[
DEMO
](
http://demo.jumpserver.org
)
[
截图
](
http://docs.jumpserver.org/zh/docs/snapshot.html
)
### SDK
### SDK
我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互,
...
...
apps/__init__.py
View file @
2ecfecb0
...
...
@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
#
__version__
=
"1.3.
2
"
__version__
=
"1.3.
3
"
apps/assets/models/asset.py
View file @
2ecfecb0
...
...
@@ -142,7 +142,7 @@ class Asset(OrgModelMixin):
return
False
,
warning
def
is_unixlike
(
self
):
if
self
.
platform
not
in
(
"Windows"
,):
if
self
.
platform
not
in
(
"Windows"
,
"Windows2016"
):
return
True
else
:
return
False
...
...
apps/assets/serializers/system_user.py
View file @
2ecfecb0
...
...
@@ -53,7 +53,7 @@ class SystemUserAuthSerializer(AuthSerializer):
model
=
SystemUser
fields
=
[
"id"
,
"name"
,
"username"
,
"protocol"
,
"password"
,
"private_key"
,
"
login_mode"
,
"
password"
,
"private_key"
,
]
...
...
apps/assets/templates/assets/gateway_create_update.html
View file @
2ecfecb0
...
...
@@ -42,7 +42,7 @@
{% bootstrap_field form.domain layout="horizontal" %}
{% block auth %}
<h3>
{% trans 'Auth' %}
</h3>
<h3
id=
"auth_title"
>
{% trans 'Auth' %}
</h3>
<div
class=
"auth-fields"
>
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.password layout="horizontal" %}
...
...
@@ -72,14 +72,23 @@
var
protocol_id
=
'#'
+
'{{ form.protocol.id_for_label }}'
;
var
private_key_id
=
'#'
+
'{{ form.private_key_file.id_for_label }}'
;
var
port
=
'#'
+
'{{ form.port.id_for_label }}'
;
var
username
=
'#'
+
'{{ form.username.id_for_label }}'
;
var
password
=
'#'
+
'{{ form.password.id_for_label }}'
;
var
auth_title
=
'#auth_title'
;
function
protocolChange
()
{
if
(
$
(
protocol_id
+
" option:selected"
).
text
()
===
'rdp'
)
{
{
#
$
(
port
).
val
(
3389
);
#
}
$
(
private_key_id
).
closest
(
'.form-group'
).
addClass
(
'hidden'
)
$
(
private_key_id
).
closest
(
'.form-group'
).
addClass
(
'hidden'
);
$
(
username
).
closest
(
'.form-group'
).
addClass
(
'hidden'
);
$
(
password
).
closest
(
'.form-group'
).
addClass
(
'hidden'
);
$
(
auth_title
).
addClass
(
'hidden'
);
}
else
{
{
#
$
(
port
).
val
(
22
);
#
}
$
(
private_key_id
).
closest
(
'.form-group'
).
removeClass
(
'hidden'
)
$
(
private_key_id
).
closest
(
'.form-group'
).
removeClass
(
'hidden'
);
$
(
username
).
closest
(
'.form-group'
).
removeClass
(
'hidden'
);
$
(
password
).
closest
(
'.form-group'
).
removeClass
(
'hidden'
);
$
(
auth_title
).
removeClass
(
'hidden'
);
}
}
...
...
apps/assets/templates/assets/system_user_detail.html
View file @
2ecfecb0
...
...
@@ -152,15 +152,14 @@
</span>
</td>
</tr>
<tr>
<td
width=
"50%"
>
{% trans 'Clear auth' %}:
</td>
<td>
<span
style=
"float: right"
>
<button
type=
"button"
class=
"btn btn-primary btn-xs btn-clear-auth"
style=
"width: 54px"
>
{% trans 'Clear' %}
</button>
</span>
</td>
</tr>
{#
<tr>
#}
{#
<td
width=
"50%"
>
{% trans 'Clear auth' %}:
</td>
#}
{#
<td>
#}
{#
<span
style=
"float: right"
>
#}
{#
<button
type=
"button"
class=
"btn btn-primary btn-xs btn-clear-auth"
style=
"width: 54px"
>
{% trans 'Clear' %}
</button>
#}
{#
</span>
#}
{#
</td>
#}
{#
</tr>
#}
{#
<tr>
#}
{#
<td
width=
"50%"
>
{% trans 'Change auth period' %}:
</td>
#}
...
...
apps/assets/urls/views_urls.py
View file @
2ecfecb0
...
...
@@ -50,4 +50,3 @@ urlpatterns = [
url
(
r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/create/$'
,
views
.
DomainGatewayCreateView
.
as_view
(),
name
=
'domain-gateway-create'
),
url
(
r'^domain/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/update/$'
,
views
.
DomainGatewayUpdateView
.
as_view
(),
name
=
'domain-gateway-update'
),
]
apps/common/mixins.py
View file @
2ecfecb0
...
...
@@ -92,7 +92,7 @@ class DatetimeSearchMixin:
date_format
=
'
%
Y-
%
m-
%
d'
date_from
=
date_to
=
None
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
def
get
_date_range
(
self
):
date_from_s
=
self
.
request
.
GET
.
get
(
'date_from'
)
date_to_s
=
self
.
request
.
GET
.
get
(
'date_to'
)
...
...
@@ -112,6 +112,9 @@ class DatetimeSearchMixin:
)
else
:
self
.
date_to
=
timezone
.
now
()
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
get_date_range
()
return
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
...
...
apps/i18n/zh/LC_MESSAGES/django.mo
View file @
2ecfecb0
No preview for this file type
apps/i18n/zh/LC_MESSAGES/django.po
View file @
2ecfecb0
This diff is collapsed.
Click to expand it.
apps/jumpserver/settings.py
View file @
2ecfecb0
...
...
@@ -341,6 +341,7 @@ AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
AUTH_LDAP_CONNECTION_OPTIONS
=
{
ldap
.
OPT_TIMEOUT
:
5
}
AUTH_LDAP_GROUP_CACHE_TIMEOUT
=
1
AUTH_LDAP_ALWAYS_UPDATE_USER
=
True
AUTH_LDAP_BACKEND
=
'django_auth_ldap.backend.LDAPBackend'
...
...
apps/jumpserver/views.py
View file @
2ecfecb0
...
...
@@ -10,6 +10,7 @@ from users.models import User
from
assets.models
import
Asset
from
terminal.models
import
Session
from
common.permissions
import
AdminUserRequiredMixin
from
orgs.utils
import
current_org
class
IndexView
(
AdminUserRequiredMixin
,
TemplateView
):
...
...
@@ -27,7 +28,7 @@ class IndexView(AdminUserRequiredMixin, TemplateView):
@staticmethod
def
get_user_count
():
return
User
.
objects
.
filter
(
role__in
=
(
'Admin'
,
'User'
)
)
.
count
()
return
current_org
.
get_org_users
(
)
.
count
()
@staticmethod
def
get_asset_count
():
...
...
@@ -49,7 +50,6 @@ class IndexView(AdminUserRequiredMixin, TemplateView):
def
get_week_login_asset_count
(
self
):
return
self
.
session_week
.
count
()
# return self.session_week.values('asset').distinct().count()
def
get_month_day_metrics
(
self
):
month_str
=
[
d
.
strftime
(
'
%
m-
%
d'
)
for
d
in
self
.
session_month_dates
]
or
[
'0'
]
...
...
apps/orgs/models.py
View file @
2ecfecb0
...
...
@@ -55,9 +55,11 @@ class Organization(models.Model):
def
get_org_users
(
self
):
from
users.models
import
User
if
self
.
is_default
():
return
User
.
objects
.
filter
(
orgs__isnull
=
True
)
users
=
User
.
objects
.
filter
(
orgs__isnull
=
True
)
else
:
return
self
.
users
.
all
()
users
=
self
.
users
.
all
()
users
=
users
.
exclude
(
role
=
User
.
ROLE_APP
)
return
users
def
get_org_admins
(
self
):
if
self
.
is_real
():
...
...
apps/perms/api.py
View file @
2ecfecb0
...
...
@@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404
from
rest_framework.views
import
APIView
,
Response
from
rest_framework.generics
import
ListAPIView
,
get_object_or_404
,
RetrieveUpdateAPIView
from
rest_framework
import
viewsets
from
rest_framework.pagination
import
LimitOffsetPagination
from
common.utils
import
set_or_append_attr_bulk
,
get_object_or_none
from
common.permissions
import
IsValidUser
,
IsOrgAdmin
,
IsOrgAdminOrAppUser
...
...
@@ -72,10 +73,7 @@ class UserGrantedAssetsApi(ListAPIView):
util
=
AssetPermissionUtil
(
user
)
for
k
,
v
in
util
.
get_assets
()
.
items
():
if
k
.
is_unixlike
():
system_users_granted
=
[
s
for
s
in
v
if
s
.
protocol
in
[
'ssh'
,
'telnet'
]]
else
:
system_users_granted
=
[
s
for
s
in
v
if
s
.
protocol
in
[
'rdp'
,
'telnet'
]]
system_users_granted
=
[
s
for
s
in
v
if
s
.
protocol
==
k
.
protocol
]
k
.
system_users_granted
=
system_users_granted
queryset
.
append
(
k
)
return
queryset
...
...
@@ -123,10 +121,7 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
for
node
,
_assets
in
nodes
.
items
():
assets
=
_assets
.
keys
()
for
k
,
v
in
_assets
.
items
():
if
k
.
is_unixlike
():
system_users_granted
=
[
s
for
s
in
v
if
s
.
protocol
in
[
'ssh'
,
'telnet'
]]
else
:
system_users_granted
=
[
s
for
s
in
v
if
s
.
protocol
in
[
'rdp'
,
'telnet'
]]
system_users_granted
=
[
s
for
s
in
v
if
s
.
protocol
==
k
.
protocol
]
k
.
system_users_granted
=
system_users_granted
node
.
assets_granted
=
assets
queryset
.
append
(
node
)
...
...
apps/perms/urls/views_urls.py
View file @
2ecfecb0
...
...
@@ -6,13 +6,11 @@ from .. import views
app_name
=
'perms'
urlpatterns
=
[
url
(
r'^asset-permission$'
,
views
.
AssetPermissionListView
.
as_view
(),
name
=
'asset-permission-list'
),
url
(
r'^asset-permission/create$'
,
views
.
AssetPermissionCreateView
.
as_view
(),
name
=
'asset-permission-create'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update$'
,
views
.
AssetPermissionUpdateView
.
as_view
(),
name
=
'asset-permission-update'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})$'
,
views
.
AssetPermissionDetailView
.
as_view
(),
name
=
'asset-permission-detail'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete$'
,
views
.
AssetPermissionDeleteView
.
as_view
(),
name
=
'asset-permission-delete'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user$'
,
views
.
AssetPermissionUserView
.
as_view
(),
name
=
'asset-permission-user-list'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset$'
,
views
.
AssetPermissionAssetView
.
as_view
(),
name
=
'asset-permission-asset-list'
),
url
(
r'^asset-permission
/
$'
,
views
.
AssetPermissionListView
.
as_view
(),
name
=
'asset-permission-list'
),
url
(
r'^asset-permission/create
/
$'
,
views
.
AssetPermissionCreateView
.
as_view
(),
name
=
'asset-permission-create'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update
/
$'
,
views
.
AssetPermissionUpdateView
.
as_view
(),
name
=
'asset-permission-update'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})
/
$'
,
views
.
AssetPermissionDetailView
.
as_view
(),
name
=
'asset-permission-detail'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete
/
$'
,
views
.
AssetPermissionDeleteView
.
as_view
(),
name
=
'asset-permission-delete'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user
/
$'
,
views
.
AssetPermissionUserView
.
as_view
(),
name
=
'asset-permission-user-list'
),
url
(
r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset
/
$'
,
views
.
AssetPermissionAssetView
.
as_view
(),
name
=
'asset-permission-asset-list'
),
]
apps/templates/_footer.html
View file @
2ecfecb0
<div
class=
"footer fixed"
>
<div
class=
"pull-right"
>
Version
<strong>
1.3.
2
-{% include '_build.html' %}
</strong>
GPLv2.
Version
<strong>
1.3.
3
-{% include '_build.html' %}
</strong>
GPLv2.
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div>
<div>
...
...
apps/terminal/api.py
View file @
2ecfecb0
...
...
@@ -4,6 +4,7 @@ from collections import OrderedDict
import
logging
import
os
import
uuid
import
copy
from
django.core.cache
import
cache
from
django.shortcuts
import
get_object_or_404
,
redirect
...
...
@@ -310,6 +311,7 @@ class SessionReplayViewSet(viewsets.ViewSet):
def
retrieve
(
self
,
request
,
*
args
,
**
kwargs
):
session_id
=
kwargs
.
get
(
'pk'
)
self
.
session
=
get_object_or_404
(
Session
,
id
=
session_id
)
# 新版本和老版本的文件后缀不同
session_path
=
self
.
get_session_path
()
# 存在外部存储上的路径
local_path
=
self
.
get_local_path
()
...
...
apps/terminal/templates/terminal/command_list.html
View file @
2ecfecb0
...
...
@@ -92,27 +92,52 @@
{% endfor %}
</tbody>
</table>
<div
id=
"actions"
class=
""
>
<div
class=
"input-group"
>
<select
class=
"form-control m-b"
style=
"width: auto"
id=
"slct_bulk_update"
>
<option
value=
"export"
>
{% trans 'Export command' %}
</option>
</select>
<div
class=
"input-group-btn pull-left"
style=
"padding-left: 5px;"
>
<button
id=
'btn_bulk_update'
style=
"height: 32px;"
class=
"btn btn-sm btn-primary"
>
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script
src=
"{% static "
js
/
plugins
/
footable
/
footable
.
all
.
min
.
js
"
%}"
></script>
<script
src=
"{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"
></script>
<script>
$
(
document
).
ready
(
function
()
{
$
(
'.footable'
).
footable
();
$
(
'.select2'
).
select2
({
dropdownAutoWidth
:
true
,
width
:
'auto'
});
$
(
'#date .input-daterange'
).
datepicker
({
format
:
"yyyy-mm-dd"
,
todayBtn
:
"linked"
,
keyboardNavigation
:
false
,
forceParse
:
false
,
calendarWeeks
:
true
,
autoclose
:
true
});
$
(
document
).
ready
(
function
()
{
$
(
'.footable'
).
footable
();
$
(
'.select2'
).
select2
({
dropdownAutoWidth
:
true
,
width
:
'auto'
});
$
(
'#date .input-daterange'
).
datepicker
({
format
:
"yyyy-mm-dd"
,
todayBtn
:
"linked"
,
keyboardNavigation
:
false
,
forceParse
:
false
,
calendarWeeks
:
true
,
autoclose
:
true
});
})
.
on
(
'click'
,
'#btn_bulk_update'
,
function
(){
var
action
=
$
(
'#slct_bulk_update'
).
val
();
var
param_action
=
'&action='
+
action
;
var
local_params
=
window
.
location
.
search
;
if
(
!
local_params
){
param_action
=
'?action='
+
action
;
}
var
params
=
local_params
+
param_action
;
var
pathname
=
window
.
location
.
pathname
+
'export/'
;
var
url
=
pathname
+
params
;
window
.
open
(
url
);
});
</script>
{% endblock %}
...
...
apps/terminal/templates/terminal/command_report.html
0 → 100644
View file @
2ecfecb0
{% load common_tags %}
{% load static %}
<!DOCTYPE html>
<html
lang=
"en"
>
<head>
<meta
charset=
"UTF-8"
>
<title>
Command Report
</title>
<style>
*
{
margin
:
0
;
padding
:
0
;
}
.background
{
background-color
:
#535659
;
padding-top
:
50px
;
padding-bottom
:
50px
;
}
.paper
{
margin-left
:
23%
;
margin-right
:
24%
;
border
:
solid
;
padding
:
50px
;
background-color
:
#fff
;
}
h2
{
font-style
:
italic
;
text-align
:
center
;
}
.info
{
width
:
200px
;
margin-left
:
450px
;
font-style
:
italic
;
text-align
:
left
;
padding-top
:
20px
;
padding-bottom
:
20px
;
}
.command
{
margin-left
:
10px
;
}
.command-desc
{
font-size
:
12px
;
}
.command-desc
span
{
float
:
right
;
}
.command-input
{
{#
font-style
:
italic
;
#
}
font-size
:
15px
;
margin-top
:
10px
;
margin-bottom
:
10px
;
}
.command-input
span
{
font-size
:
13px
;
}
.hr-line-dashed
{
border-top
:
1px
dashed
#000
;
color
:
#000
;
background-color
:
#fff
;
height
:
1px
;
margin
:
20px
0
;
}
pre
{
font-size
:
12px
;
}
</style>
</head>
<body>
<div
class=
"background"
>
<div
class=
"paper"
>
<h2>
Command Report
</h2>
<div
class=
"info"
>
<p>
total: {{ total_count }}
</p>
<p>
date: {{ now | ts_to_date }}
</p>
</div>
<div
class=
"hr-line-dashed"
></div>
<div>
{% for command in queryset %}
<div
class=
"command"
>
<p
class=
"command-desc"
>
[{{ command.user}} {{ command.system_user }}@{{ command.asset }} {{ command.timestamp | ts_to_date }}]
<span>
{{ forloop.counter }}
</span>
</p>
<p
class=
"command-input"
><span>
$
</span>
{{ command.input }}
</p>
<pre>
{{ command.output }}
</pre>
</div>
<div
class=
"hr-line-dashed"
></div>
{% endfor %}
</div>
</div>
</div>
</body>
</html>
\ No newline at end of file
apps/terminal/urls/views_urls.py
View file @
2ecfecb0
...
...
@@ -24,5 +24,6 @@ urlpatterns = [
# Command view
url
(
r'^command/$'
,
views
.
CommandListView
.
as_view
(),
name
=
'command-list'
),
url
(
r'^command/export/$'
,
views
.
CommandExportView
.
as_view
(),
name
=
'command-export'
)
]
apps/terminal/views/command.py
View file @
2ecfecb0
# -*- coding: utf-8 -*-
#
from
django.views.generic
import
ListView
from
django.views.generic
import
ListView
,
View
from
django.conf
import
settings
from
django.utils
import
timezone
from
django.utils.translation
import
ugettext
as
_
from
django.http
import
HttpResponse
from
django.template
import
loader
import
time
from
common.mixins
import
DatetimeSearchMixin
from
common.permissions
import
AdminUserRequiredMixin
...
...
@@ -12,7 +14,7 @@ from ..models import Command
from
..
import
utils
from
..backends
import
get_multi_command_storage
__all__
=
[
'CommandListView'
]
__all__
=
[
'CommandListView'
,
'CommandExportView'
]
common_storage
=
get_multi_command_storage
()
...
...
@@ -61,7 +63,43 @@ class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
return
super
()
.
get_context_data
(
**
kwargs
)
class
CommandExportView
(
DatetimeSearchMixin
,
AdminUserRequiredMixin
,
View
):
model
=
Command
command
=
user
=
asset
=
system_user
=
action
=
''
date_from
=
date_to
=
None
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
queryset
=
self
.
get_queryset
()
template
=
'terminal/command_report.html'
context
=
{
'queryset'
:
queryset
,
'total_count'
:
len
(
queryset
),
'now'
:
time
.
time
(),
}
content
=
loader
.
render_to_string
(
template
,
context
,
request
)
content_type
=
'application/octet-stream'
response
=
HttpResponse
(
content
,
content_type
)
filename
=
'command-report-{}.html'
.
format
(
int
(
time
.
time
()))
response
[
'Content-Disposition'
]
=
'attachment; filename="
%
s"'
%
filename
return
response
def
get_queryset
(
self
):
self
.
get_date_range
()
self
.
action
=
self
.
request
.
GET
.
get
(
'action'
,
''
)
self
.
command
=
self
.
request
.
GET
.
get
(
'command'
,
''
)
self
.
user
=
self
.
request
.
GET
.
get
(
"user"
,
''
)
self
.
asset
=
self
.
request
.
GET
.
get
(
'asset'
,
''
)
self
.
system_user
=
self
.
request
.
GET
.
get
(
'system_user'
,
''
)
filter_kwargs
=
dict
()
filter_kwargs
[
'date_from'
]
=
self
.
date_from
filter_kwargs
[
'date_to'
]
=
self
.
date_to
if
self
.
user
:
filter_kwargs
[
'user'
]
=
self
.
user
if
self
.
asset
:
filter_kwargs
[
'asset'
]
=
self
.
asset
if
self
.
system_user
:
filter_kwargs
[
'system_user'
]
=
self
.
system_user
if
self
.
command
:
filter_kwargs
[
'input'
]
=
self
.
command
queryset
=
common_storage
.
filter
(
**
filter_kwargs
)
return
queryset
apps/users/api.py
View file @
2ecfecb0
...
...
@@ -101,7 +101,23 @@ class UserUpdatePKApi(generics.UpdateAPIView):
user
.
save
()
class
UserGroupViewSet
(
BulkModelViewSet
):
class
UserUnblockPKApi
(
generics
.
UpdateAPIView
):
queryset
=
User
.
objects
.
all
()
permission_classes
=
(
IsSuperUser
,)
serializer_class
=
UserSerializer
key_prefix_limit
=
"_LOGIN_LIMIT_{}_{}"
key_prefix_block
=
"_LOGIN_BLOCK_{}"
def
perform_update
(
self
,
serializer
):
user
=
self
.
get_object
()
username
=
user
.
username
if
user
else
''
key_limit
=
self
.
key_prefix_limit
.
format
(
username
,
'*'
)
key_block
=
self
.
key_prefix_block
.
format
(
username
)
cache
.
delete_pattern
(
key_limit
)
cache
.
delete
(
key_block
)
class
UserGroupViewSet
(
IDInFilterMixin
,
BulkModelViewSet
):
queryset
=
UserGroup
.
objects
.
all
()
serializer_class
=
UserGroupSerializer
permission_classes
=
(
IsOrgAdmin
,)
...
...
@@ -203,13 +219,15 @@ class UserAuthApi(APIView):
permission_classes
=
(
AllowAny
,)
serializer_class
=
UserSerializer
key_prefix_limit
=
"_LOGIN_LIMIT_{}_{}"
key_prefix_block
=
"_LOGIN_BLOCK_{}"
def
post
(
self
,
request
):
# limit login
username
=
request
.
data
.
get
(
'username'
)
ip
=
request
.
data
.
get
(
'remote_addr'
,
None
)
ip
=
ip
if
ip
else
get_login_ip
(
request
)
key_limit
=
self
.
key_prefix_limit
.
format
(
ip
,
username
)
key_limit
=
self
.
key_prefix_limit
.
format
(
username
,
ip
)
key_block
=
self
.
key_prefix_block
.
format
(
username
)
if
is_block_login
(
key_limit
):
msg
=
_
(
"Log in frequently and try again later"
)
return
Response
({
'msg'
:
msg
},
status
=
401
)
...
...
@@ -224,7 +242,7 @@ class UserAuthApi(APIView):
}
self
.
write_login_log
(
request
,
data
)
set_user_login_failed_count_to_cache
(
key_limit
)
set_user_login_failed_count_to_cache
(
key_limit
,
key_block
)
return
Response
({
'msg'
:
msg
},
status
=
401
)
if
not
user
.
otp_enabled
:
...
...
apps/users/templates/users/user_detail.html
View file @
2ecfecb0
...
...
@@ -182,6 +182,14 @@
</span>
</td>
</tr>
<tr
style=
"{% if not unblock %}display:none{% endif %}"
>
<td>
{% trans 'Unblock user' %}
</td>
<td>
<span
class=
"pull-right"
>
<button
type=
"button"
class=
"btn btn-primary btn-xs"
id=
"btn-unblock-user"
style=
"width: 54px"
>
{% trans 'Unblock' %}
</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
...
...
@@ -275,7 +283,7 @@ $(document).ready(function() {
.
on
(
'select2:unselect'
,
function
(
evt
)
{
var
data
=
evt
.
params
.
data
;
delete
jumpserver
.
nodes_selected
[
data
.
id
];
})
})
;
})
.
on
(
'click'
,
'#is_active'
,
function
()
{
var
the_url
=
"{% url 'api-users:user-detail' pk=user_object.id %}"
;
...
...
@@ -293,7 +301,7 @@ $(document).ready(function() {
.
on
(
'click'
,
'#force_enable_otp'
,
function
()
{
{
%
if
request
.
user
==
user_object
%
}
toastr
.
error
(
"{% trans 'Goto profile page enable MFA' %}"
);
return
return
;
{
%
endif
%
}
var
the_url
=
"{% url 'api-users:user-detail' pk=user_object.id %}"
;
...
...
@@ -426,6 +434,40 @@ $(document).ready(function() {
var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'users:user-list' %}";
objectDelete($this, name, the_url, redirect_url);
}).on('click', '#btn-unblock-user', function () {
function doReset() {
{#var the_url = '{% url "api-users:user-reset-password" pk=user_object.id %}';#}
var the_url = '{% url "api-users:user-unblock" pk=user_object.id %}';
var body = {};
var success = function() {
var msg = "{% trans "Success" %}";
{#swal("{% trans 'Unblock user' %}", msg, "success");#}
swal({
title: "{% trans 'Unblock user' %}",
text: msg,
type: "success"
}, function() {
location.reload()
}
);
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans "After unlocking the user, the user can log in normally."%}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
doReset();
});
})
</script>
{% endblock %}
apps/users/templates/users/user_list.html
View file @
2ecfecb0
...
...
@@ -59,7 +59,7 @@ function initTable() {
ele
:
$
(
'#user_list_table'
),
columnDefs
:
[
{
targets
:
1
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
detail_btn
=
'<a href="{% url "users:user-detail" pk=DEFAULT_PK %}">'
+
escape
(
cellData
)
+
'</a>'
;
var
detail_btn
=
'<a href="{% url "users:user-detail" pk=DEFAULT_PK %}">'
+
cellData
+
'</a>'
;
$
(
td
).
html
(
detail_btn
.
replace
(
"{{ DEFAULT_PK }}"
,
rowData
.
id
));
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
...
...
apps/users/urls/api_urls.py
View file @
2ecfecb0
...
...
@@ -29,6 +29,8 @@ urlpatterns = [
api
.
UserResetPKApi
.
as_view
(),
name
=
'user-public-key-reset'
),
url
(
r'^users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/update/$'
,
api
.
UserUpdatePKApi
.
as_view
(),
name
=
'user-public-key-update'
),
url
(
r'^users/(?P<pk>[0-9a-zA-Z\-]{36})/unblock/$'
,
api
.
UserUnblockPKApi
.
as_view
(),
name
=
'user-unblock'
),
url
(
r'^users/(?P<pk>[0-9a-zA-Z\-]{36})/groups/$'
,
api
.
UserUpdateGroupApi
.
as_view
(),
name
=
'user-update-group'
),
url
(
r'^groups/(?P<pk>[0-9a-zA-Z\-]{36})/users/$'
,
...
...
apps/users/urls/views_urls.py
View file @
2ecfecb0
...
...
@@ -8,13 +8,13 @@ app_name = 'users'
urlpatterns
=
[
# Login view
url
(
r'^login$'
,
views
.
UserLoginView
.
as_view
(),
name
=
'login'
),
url
(
r'^logout$'
,
views
.
UserLogoutView
.
as_view
(),
name
=
'logout'
),
url
(
r'^login/otp$'
,
views
.
UserLoginOtpView
.
as_view
(),
name
=
'login-otp'
),
url
(
r'^password/forgot$'
,
views
.
UserForgotPasswordView
.
as_view
(),
name
=
'forgot-password'
),
url
(
r'^password/forgot/sendmail-success$'
,
views
.
UserForgotPasswordSendmailSuccessView
.
as_view
(),
name
=
'forgot-password-sendmail-success'
),
url
(
r'^password/reset$'
,
views
.
UserResetPasswordView
.
as_view
(),
name
=
'reset-password'
),
url
(
r'^password/reset/success$'
,
views
.
UserResetPasswordSuccessView
.
as_view
(),
name
=
'reset-password-success'
),
url
(
r'^login
/
$'
,
views
.
UserLoginView
.
as_view
(),
name
=
'login'
),
url
(
r'^logout
/
$'
,
views
.
UserLogoutView
.
as_view
(),
name
=
'logout'
),
url
(
r'^login/otp
/
$'
,
views
.
UserLoginOtpView
.
as_view
(),
name
=
'login-otp'
),
url
(
r'^password/forgot
/
$'
,
views
.
UserForgotPasswordView
.
as_view
(),
name
=
'forgot-password'
),
url
(
r'^password/forgot/sendmail-success
/
$'
,
views
.
UserForgotPasswordSendmailSuccessView
.
as_view
(),
name
=
'forgot-password-sendmail-success'
),
url
(
r'^password/reset
/
$'
,
views
.
UserResetPasswordView
.
as_view
(),
name
=
'reset-password'
),
url
(
r'^password/reset/success
/
$'
,
views
.
UserResetPasswordSuccessView
.
as_view
(),
name
=
'reset-password-success'
),
# Profile
url
(
r'^profile/$'
,
views
.
UserProfileView
.
as_view
(),
name
=
'user-profile'
),
...
...
@@ -29,23 +29,23 @@ urlpatterns = [
url
(
r'^profile/otp/settings-success/$'
,
views
.
UserOtpSettingsSuccessView
.
as_view
(),
name
=
'user-otp-settings-success'
),
# User view
url
(
r'^user$'
,
views
.
UserListView
.
as_view
(),
name
=
'user-list'
),
url
(
r'^user/export/'
,
views
.
UserExportView
.
as_view
(),
name
=
'user-export'
),
url
(
r'^user
/
$'
,
views
.
UserListView
.
as_view
(),
name
=
'user-list'
),
url
(
r'^user/export/
$
'
,
views
.
UserExportView
.
as_view
(),
name
=
'user-export'
),
url
(
r'^first-login/$'
,
views
.
UserFirstLoginView
.
as_view
(),
name
=
'user-first-login'
),
url
(
r'^user/import/$'
,
views
.
UserBulkImportView
.
as_view
(),
name
=
'user-import'
),
url
(
r'^user/create$'
,
views
.
UserCreateView
.
as_view
(),
name
=
'user-create'
),
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/update$'
,
views
.
UserUpdateView
.
as_view
(),
name
=
'user-update'
),
url
(
r'^user/update$'
,
views
.
UserBulkUpdateView
.
as_view
(),
name
=
'user-bulk-update'
),
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})$'
,
views
.
UserDetailView
.
as_view
(),
name
=
'user-detail'
),
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/assets'
,
views
.
UserGrantedAssetView
.
as_view
(),
name
=
'user-granted-asset'
),
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/login-history'
,
views
.
UserDetailView
.
as_view
(),
name
=
'user-login-history'
),
url
(
r'^user/create
/
$'
,
views
.
UserCreateView
.
as_view
(),
name
=
'user-create'
),
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/update
/
$'
,
views
.
UserUpdateView
.
as_view
(),
name
=
'user-update'
),
url
(
r'^user/update
/
$'
,
views
.
UserBulkUpdateView
.
as_view
(),
name
=
'user-bulk-update'
),
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})
/
$'
,
views
.
UserDetailView
.
as_view
(),
name
=
'user-detail'
),
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/assets
/$
'
,
views
.
UserGrantedAssetView
.
as_view
(),
name
=
'user-granted-asset'
),
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/login-history
/$
'
,
views
.
UserDetailView
.
as_view
(),
name
=
'user-login-history'
),
# User group view
url
(
r'^user-group$'
,
views
.
UserGroupListView
.
as_view
(),
name
=
'user-group-list'
),
url
(
r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})$'
,
views
.
UserGroupDetailView
.
as_view
(),
name
=
'user-group-detail'
),
url
(
r'^user-group/create$'
,
views
.
UserGroupCreateView
.
as_view
(),
name
=
'user-group-create'
),
url
(
r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/update$'
,
views
.
UserGroupUpdateView
.
as_view
(),
name
=
'user-group-update'
),
url
(
r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets'
,
views
.
UserGroupGrantedAssetView
.
as_view
(),
name
=
'user-group-granted-asset'
),
url
(
r'^user-group
/
$'
,
views
.
UserGroupListView
.
as_view
(),
name
=
'user-group-list'
),
url
(
r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})
/
$'
,
views
.
UserGroupDetailView
.
as_view
(),
name
=
'user-group-detail'
),
url
(
r'^user-group/create
/
$'
,
views
.
UserGroupCreateView
.
as_view
(),
name
=
'user-group-create'
),
url
(
r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/update
/
$'
,
views
.
UserGroupUpdateView
.
as_view
(),
name
=
'user-group-update'
),
url
(
r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets
/$
'
,
views
.
UserGroupGrantedAssetView
.
as_view
(),
name
=
'user-group-granted-asset'
),
# Login log
url
(
r'^login-log/$'
,
views
.
LoginLogListView
.
as_view
(),
name
=
'login-log-list'
),
...
...
apps/users/utils.py
View file @
2ecfecb0
...
...
@@ -212,10 +212,10 @@ def write_login_log(*args, **kwargs):
def
get_ip_city
(
ip
,
timeout
=
10
):
# Taobao ip api: http://ip.taobao.com/
/
service/getIpInfo.php?ip=8.8.8.8
# Taobao ip api: http://ip.taobao.com/service/getIpInfo.php?ip=8.8.8.8
# Sina ip api: http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=8.8.8.8&format=json
url
=
'http://i
nt.dpool.sina.com.cn/iplookup/iplookup.php?ip=
%
s&format=json
'
%
ip
url
=
'http://i
p.taobao.com/service/getIpInfo.php?ip=
%
s
'
%
ip
try
:
r
=
requests
.
get
(
url
,
timeout
=
timeout
)
except
:
...
...
@@ -224,8 +224,8 @@ def get_ip_city(ip, timeout=10):
if
r
and
r
.
status_code
==
200
:
try
:
data
=
r
.
json
()
if
not
isinstance
(
data
,
int
)
and
data
[
'
ret'
]
==
1
:
city
=
data
[
'
country'
]
+
' '
+
data
[
'city'
]
if
not
isinstance
(
data
,
int
)
and
data
[
'
code'
]
==
0
:
city
=
data
[
'
data'
][
'country'
]
+
' '
+
data
[
'data'
]
[
'city'
]
except
ValueError
:
pass
return
city
...
...
@@ -333,7 +333,7 @@ def check_password_rules(password):
return
bool
(
match_obj
)
def
set_user_login_failed_count_to_cache
(
key_limit
):
def
set_user_login_failed_count_to_cache
(
key_limit
,
key_block
):
count
=
cache
.
get
(
key_limit
)
count
=
count
+
1
if
count
else
1
...
...
@@ -343,6 +343,15 @@ def set_user_login_failed_count_to_cache(key_limit):
limit_time
=
setting_limit_time
.
cleaned_value
if
setting_limit_time
\
else
settings
.
DEFAULT_LOGIN_LIMIT_TIME
setting_limit_count
=
Setting
.
objects
.
filter
(
name
=
'SECURITY_LOGIN_LIMIT_COUNT'
)
.
first
()
limit_count
=
setting_limit_count
.
cleaned_value
if
setting_limit_count
\
else
settings
.
DEFAULT_LOGIN_LIMIT_COUNT
if
count
>=
limit_count
:
cache
.
set
(
key_block
,
1
,
int
(
limit_time
)
*
60
)
cache
.
set
(
key_limit
,
count
,
int
(
limit_time
)
*
60
)
...
...
@@ -357,3 +366,9 @@ def is_block_login(key_limit):
if
count
and
count
>=
limit_count
:
return
True
def
is_need_unblock
(
key_block
):
if
not
cache
.
get
(
key_block
):
return
False
return
True
apps/users/views/login.py
View file @
2ecfecb0
...
...
@@ -52,6 +52,7 @@ class UserLoginView(FormView):
redirect_field_name
=
'next'
key_prefix_captcha
=
"_LOGIN_INVALID_{}"
key_prefix_limit
=
"_LOGIN_LIMIT_{}_{}"
key_prefix_block
=
"_LOGIN_BLOCK_{}"
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
if
request
.
user
.
is_staff
:
...
...
@@ -65,7 +66,7 @@ class UserLoginView(FormView):
# limit login authentication
ip
=
get_login_ip
(
request
)
username
=
self
.
request
.
POST
.
get
(
'username'
)
key_limit
=
self
.
key_prefix_limit
.
format
(
ip
,
username
)
key_limit
=
self
.
key_prefix_limit
.
format
(
username
,
ip
)
if
is_block_login
(
key_limit
):
return
self
.
render_to_response
(
self
.
get_context_data
(
block_login
=
True
))
...
...
@@ -91,8 +92,9 @@ class UserLoginView(FormView):
# limit user login failed count
ip
=
get_login_ip
(
self
.
request
)
key_limit
=
self
.
key_prefix_limit
.
format
(
ip
,
username
)
set_user_login_failed_count_to_cache
(
key_limit
)
key_limit
=
self
.
key_prefix_limit
.
format
(
username
,
ip
)
key_block
=
self
.
key_prefix_block
.
format
(
username
)
set_user_login_failed_count_to_cache
(
key_limit
,
key_block
)
# show captcha
cache
.
set
(
self
.
key_prefix_captcha
.
format
(
ip
),
1
,
3600
)
...
...
apps/users/views/user.py
View file @
2ecfecb0
...
...
@@ -37,7 +37,9 @@ from common.models import Setting
from
common.permissions
import
AdminUserRequiredMixin
from
..
import
forms
from
..models
import
User
,
UserGroup
from
..utils
import
generate_otp_uri
,
check_otp_code
,
get_user_or_tmp_user
,
get_password_check_rules
,
check_password_rules
from
..utils
import
generate_otp_uri
,
check_otp_code
,
\
get_user_or_tmp_user
,
get_password_check_rules
,
check_password_rules
,
\
is_need_unblock
from
..signals
import
post_user_create
from
..tasks
import
write_login_log_async
...
...
@@ -169,13 +171,17 @@ class UserDetailView(AdminUserRequiredMixin, DetailView):
model
=
User
template_name
=
'users/user_detail.html'
context_object_name
=
"user_object"
key_prefix_block
=
"_LOGIN_BLOCK_{}"
def
get_context_data
(
self
,
**
kwargs
):
user
=
self
.
get_object
()
key_block
=
self
.
key_prefix_block
.
format
(
user
.
username
)
groups
=
UserGroup
.
objects
.
exclude
(
id__in
=
self
.
object
.
groups
.
all
())
context
=
{
'app'
:
_
(
'Users'
),
'action'
:
_
(
'User detail'
),
'groups'
:
groups
'groups'
:
groups
,
'unblock'
:
is_need_unblock
(
key_block
),
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
docs/step_by_step.rst
View file @
2ecfecb0
...
...
@@ -228,7 +228,7 @@ Luna 已改为纯前端,需要 Nginx 来运行访问
-p 8081:8080 -v /opt/guacamole/key:/config/guacamole/key \
-e JUMPSERVER_KEY_DIR=/config/guacamole/key \
-e JUMPSERVER_SERVER=http://<填写本机的IP地址>:8080 \
registry.jumpserver.org/public/guacamole:
1.0.0
registry.jumpserver.org/public/guacamole:
latest
这里所需要注意的是 guacamole 暴露出来的端口是 8081,若与主机上其他端口冲突请自定义一下。
...
...
jms
View file @
2ecfecb0
...
...
@@ -122,8 +122,8 @@ def start_gunicorn():
cmd
=
[
'gunicorn'
,
'jumpserver.wsgi'
,
'-b'
,
bind
,
'-w'
,
str
(
WORKERS
),
'-k'
,
'eventlet'
,
'-w'
,
str
(
WORKERS
),
'--access-logformat'
,
log_format
,
'-p'
,
pid_file
,
]
...
...
requirements/requirements.txt
View file @
2ecfecb0
...
...
@@ -48,7 +48,7 @@ MarkupSafe==1.0
mysqlclient==1.3.12
olefile==0.44
openapi-codec==1.3.2
paramiko==2.4.
0
paramiko==2.4.
1
passlib==1.7.1
Pillow==4.3.0
pyasn1==0.4.2
...
...
utils/make_migrations.sh
View file @
2ecfecb0
...
...
@@ -5,4 +5,4 @@ python3 ../apps/manage.py makemigrations
python3 ../apps/manage.py migrate
python3 ../apps/manage.py makemigrations
–
-merge
python3 ../apps/manage.py makemigrations
-
-merge
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