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采纳分布式架构,支持多机房跨区域部署,中心节点
...
@@ -19,25 +19,25 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
----
----
### 功能
### 功能
!
[
Jumpserver功能
](
https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver13.jpg
"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
)
也可以查看我们完整文档包括了使用和开发
[
文档
](
http://docs.jumpserver.org
)
### Demo 和 截图
### Demo 和 截图
我们提供了DEMO和截图可以让你快速了解Jumpserver
我们提供了DEMO和截图可以让你快速了解Jumpserver
[
DEMO
](
http://demo.jumpserver.org
)
[
DEMO
](
http://demo.jumpserver.org
)
[
截图
](
http://docs.jumpserver.org/zh/docs/snapshot.html
)
[
截图
](
http://docs.jumpserver.org/zh/docs/snapshot.html
)
### SDK
### SDK
我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互,
我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互,
...
...
apps/__init__.py
View file @
2ecfecb0
...
@@ -2,4 +2,4 @@
...
@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
# -*- 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):
...
@@ -142,7 +142,7 @@ class Asset(OrgModelMixin):
return
False
,
warning
return
False
,
warning
def
is_unixlike
(
self
):
def
is_unixlike
(
self
):
if
self
.
platform
not
in
(
"Windows"
,):
if
self
.
platform
not
in
(
"Windows"
,
"Windows2016"
):
return
True
return
True
else
:
else
:
return
False
return
False
...
...
apps/assets/serializers/system_user.py
View file @
2ecfecb0
...
@@ -53,7 +53,7 @@ class SystemUserAuthSerializer(AuthSerializer):
...
@@ -53,7 +53,7 @@ class SystemUserAuthSerializer(AuthSerializer):
model
=
SystemUser
model
=
SystemUser
fields
=
[
fields
=
[
"id"
,
"name"
,
"username"
,
"protocol"
,
"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 @@
...
@@ -42,7 +42,7 @@
{% bootstrap_field form.domain layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
{% block auth %}
{% block auth %}
<h3>
{% trans 'Auth' %}
</h3>
<h3
id=
"auth_title"
>
{% trans 'Auth' %}
</h3>
<div
class=
"auth-fields"
>
<div
class=
"auth-fields"
>
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.password layout="horizontal" %}
...
@@ -72,14 +72,23 @@
...
@@ -72,14 +72,23 @@
var
protocol_id
=
'#'
+
'{{ form.protocol.id_for_label }}'
;
var
protocol_id
=
'#'
+
'{{ form.protocol.id_for_label }}'
;
var
private_key_id
=
'#'
+
'{{ form.private_key_file.id_for_label }}'
;
var
private_key_id
=
'#'
+
'{{ form.private_key_file.id_for_label }}'
;
var
port
=
'#'
+
'{{ form.port.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
()
{
function
protocolChange
()
{
if
(
$
(
protocol_id
+
" option:selected"
).
text
()
===
'rdp'
)
{
if
(
$
(
protocol_id
+
" option:selected"
).
text
()
===
'rdp'
)
{
{
#
$
(
port
).
val
(
3389
);
#
}
{
#
$
(
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
{
}
else
{
{
#
$
(
port
).
val
(
22
);
#
}
{
#
$
(
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 @@
...
@@ -152,15 +152,14 @@
</span>
</span>
</td>
</td>
</tr>
</tr>
{#
<tr>
#}
<tr>
{#
<td
width=
"50%"
>
{% trans 'Clear auth' %}:
</td>
#}
<td
width=
"50%"
>
{% trans 'Clear auth' %}:
</td>
{#
<td>
#}
<td>
{#
<span
style=
"float: right"
>
#}
<span
style=
"float: right"
>
{#
<button
type=
"button"
class=
"btn btn-primary btn-xs btn-clear-auth"
style=
"width: 54px"
>
{% trans 'Clear' %}
</button>
#}
<button
type=
"button"
class=
"btn btn-primary btn-xs btn-clear-auth"
style=
"width: 54px"
>
{% trans 'Clear' %}
</button>
{#
</span>
#}
</span>
{#
</td>
#}
</td>
{#
</tr>
#}
</tr>
{#
<tr>
#}
{#
<tr>
#}
{#
<td
width=
"50%"
>
{% trans 'Change auth period' %}:
</td>
#}
{#
<td
width=
"50%"
>
{% trans 'Change auth period' %}:
</td>
#}
...
...
apps/assets/urls/views_urls.py
View file @
2ecfecb0
...
@@ -50,4 +50,3 @@ urlpatterns = [
...
@@ -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/(?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'
),
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:
...
@@ -92,7 +92,7 @@ class DatetimeSearchMixin:
date_format
=
'
%
Y-
%
m-
%
d'
date_format
=
'
%
Y-
%
m-
%
d'
date_from
=
date_to
=
None
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_from_s
=
self
.
request
.
GET
.
get
(
'date_from'
)
date_to_s
=
self
.
request
.
GET
.
get
(
'date_to'
)
date_to_s
=
self
.
request
.
GET
.
get
(
'date_to'
)
...
@@ -112,6 +112,9 @@ class DatetimeSearchMixin:
...
@@ -112,6 +112,9 @@ class DatetimeSearchMixin:
)
)
else
:
else
:
self
.
date_to
=
timezone
.
now
()
self
.
date_to
=
timezone
.
now
()
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
get_date_range
()
return
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
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(
...
@@ -341,6 +341,7 @@ AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
AUTH_LDAP_CONNECTION_OPTIONS
=
{
AUTH_LDAP_CONNECTION_OPTIONS
=
{
ldap
.
OPT_TIMEOUT
:
5
ldap
.
OPT_TIMEOUT
:
5
}
}
AUTH_LDAP_GROUP_CACHE_TIMEOUT
=
1
AUTH_LDAP_ALWAYS_UPDATE_USER
=
True
AUTH_LDAP_ALWAYS_UPDATE_USER
=
True
AUTH_LDAP_BACKEND
=
'django_auth_ldap.backend.LDAPBackend'
AUTH_LDAP_BACKEND
=
'django_auth_ldap.backend.LDAPBackend'
...
...
apps/jumpserver/views.py
View file @
2ecfecb0
...
@@ -10,6 +10,7 @@ from users.models import User
...
@@ -10,6 +10,7 @@ from users.models import User
from
assets.models
import
Asset
from
assets.models
import
Asset
from
terminal.models
import
Session
from
terminal.models
import
Session
from
common.permissions
import
AdminUserRequiredMixin
from
common.permissions
import
AdminUserRequiredMixin
from
orgs.utils
import
current_org
class
IndexView
(
AdminUserRequiredMixin
,
TemplateView
):
class
IndexView
(
AdminUserRequiredMixin
,
TemplateView
):
...
@@ -27,7 +28,7 @@ class IndexView(AdminUserRequiredMixin, TemplateView):
...
@@ -27,7 +28,7 @@ class IndexView(AdminUserRequiredMixin, TemplateView):
@staticmethod
@staticmethod
def
get_user_count
():
def
get_user_count
():
return
User
.
objects
.
filter
(
role__in
=
(
'Admin'
,
'User'
)
)
.
count
()
return
current_org
.
get_org_users
(
)
.
count
()
@staticmethod
@staticmethod
def
get_asset_count
():
def
get_asset_count
():
...
@@ -49,7 +50,6 @@ class IndexView(AdminUserRequiredMixin, TemplateView):
...
@@ -49,7 +50,6 @@ class IndexView(AdminUserRequiredMixin, TemplateView):
def
get_week_login_asset_count
(
self
):
def
get_week_login_asset_count
(
self
):
return
self
.
session_week
.
count
()
return
self
.
session_week
.
count
()
# return self.session_week.values('asset').distinct().count()
def
get_month_day_metrics
(
self
):
def
get_month_day_metrics
(
self
):
month_str
=
[
d
.
strftime
(
'
%
m-
%
d'
)
for
d
in
self
.
session_month_dates
]
or
[
'0'
]
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):
...
@@ -55,9 +55,11 @@ class Organization(models.Model):
def
get_org_users
(
self
):
def
get_org_users
(
self
):
from
users.models
import
User
from
users.models
import
User
if
self
.
is_default
():
if
self
.
is_default
():
return
User
.
objects
.
filter
(
orgs__isnull
=
True
)
users
=
User
.
objects
.
filter
(
orgs__isnull
=
True
)
else
:
else
:
return
self
.
users
.
all
()
users
=
self
.
users
.
all
()
users
=
users
.
exclude
(
role
=
User
.
ROLE_APP
)
return
users
def
get_org_admins
(
self
):
def
get_org_admins
(
self
):
if
self
.
is_real
():
if
self
.
is_real
():
...
...
apps/perms/api.py
View file @
2ecfecb0
...
@@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404
...
@@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404
from
rest_framework.views
import
APIView
,
Response
from
rest_framework.views
import
APIView
,
Response
from
rest_framework.generics
import
ListAPIView
,
get_object_or_404
,
RetrieveUpdateAPIView
from
rest_framework.generics
import
ListAPIView
,
get_object_or_404
,
RetrieveUpdateAPIView
from
rest_framework
import
viewsets
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.utils
import
set_or_append_attr_bulk
,
get_object_or_none
from
common.permissions
import
IsValidUser
,
IsOrgAdmin
,
IsOrgAdminOrAppUser
from
common.permissions
import
IsValidUser
,
IsOrgAdmin
,
IsOrgAdminOrAppUser
...
@@ -72,10 +73,7 @@ class UserGrantedAssetsApi(ListAPIView):
...
@@ -72,10 +73,7 @@ class UserGrantedAssetsApi(ListAPIView):
util
=
AssetPermissionUtil
(
user
)
util
=
AssetPermissionUtil
(
user
)
for
k
,
v
in
util
.
get_assets
()
.
items
():
for
k
,
v
in
util
.
get_assets
()
.
items
():
if
k
.
is_unixlike
():
system_users_granted
=
[
s
for
s
in
v
if
s
.
protocol
==
k
.
protocol
]
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'
]]
k
.
system_users_granted
=
system_users_granted
k
.
system_users_granted
=
system_users_granted
queryset
.
append
(
k
)
queryset
.
append
(
k
)
return
queryset
return
queryset
...
@@ -123,10 +121,7 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
...
@@ -123,10 +121,7 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
for
node
,
_assets
in
nodes
.
items
():
for
node
,
_assets
in
nodes
.
items
():
assets
=
_assets
.
keys
()
assets
=
_assets
.
keys
()
for
k
,
v
in
_assets
.
items
():
for
k
,
v
in
_assets
.
items
():
if
k
.
is_unixlike
():
system_users_granted
=
[
s
for
s
in
v
if
s
.
protocol
==
k
.
protocol
]
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'
]]
k
.
system_users_granted
=
system_users_granted
k
.
system_users_granted
=
system_users_granted
node
.
assets_granted
=
assets
node
.
assets_granted
=
assets
queryset
.
append
(
node
)
queryset
.
append
(
node
)
...
...
apps/perms/urls/views_urls.py
View file @
2ecfecb0
...
@@ -6,13 +6,11 @@ from .. import views
...
@@ -6,13 +6,11 @@ from .. import views
app_name
=
'perms'
app_name
=
'perms'
urlpatterns
=
[
urlpatterns
=
[
url
(
r'^asset-permission$'
,
views
.
AssetPermissionListView
.
as_view
(),
name
=
'asset-permission-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/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})/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})
/
$'
,
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})/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})/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/(?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=
"footer fixed"
>
<div
class=
"pull-right"
>
<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">-->
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div>
</div>
<div>
<div>
...
...
apps/terminal/api.py
View file @
2ecfecb0
...
@@ -4,6 +4,7 @@ from collections import OrderedDict
...
@@ -4,6 +4,7 @@ from collections import OrderedDict
import
logging
import
logging
import
os
import
os
import
uuid
import
uuid
import
copy
from
django.core.cache
import
cache
from
django.core.cache
import
cache
from
django.shortcuts
import
get_object_or_404
,
redirect
from
django.shortcuts
import
get_object_or_404
,
redirect
...
@@ -310,6 +311,7 @@ class SessionReplayViewSet(viewsets.ViewSet):
...
@@ -310,6 +311,7 @@ class SessionReplayViewSet(viewsets.ViewSet):
def
retrieve
(
self
,
request
,
*
args
,
**
kwargs
):
def
retrieve
(
self
,
request
,
*
args
,
**
kwargs
):
session_id
=
kwargs
.
get
(
'pk'
)
session_id
=
kwargs
.
get
(
'pk'
)
self
.
session
=
get_object_or_404
(
Session
,
id
=
session_id
)
self
.
session
=
get_object_or_404
(
Session
,
id
=
session_id
)
# 新版本和老版本的文件后缀不同
# 新版本和老版本的文件后缀不同
session_path
=
self
.
get_session_path
()
# 存在外部存储上的路径
session_path
=
self
.
get_session_path
()
# 存在外部存储上的路径
local_path
=
self
.
get_local_path
()
local_path
=
self
.
get_local_path
()
...
...
apps/terminal/templates/terminal/command_list.html
View file @
2ecfecb0
...
@@ -92,27 +92,52 @@
...
@@ -92,27 +92,52 @@
{% endfor %}
{% endfor %}
</tbody>
</tbody>
</table>
</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 %}
{% endblock %}
{% block custom_foot_js %}
{% block custom_foot_js %}
<script
src=
"{% static "
js
/
plugins
/
footable
/
footable
.
all
.
min
.
js
"
%}"
></script>
<script
src=
"{% static "
js
/
plugins
/
footable
/
footable
.
all
.
min
.
js
"
%}"
></script>
<script
src=
"{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"
></script>
<script
src=
"{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"
></script>
<script>
<script>
$
(
document
).
ready
(
function
()
{
$
(
document
).
ready
(
function
()
{
$
(
'.footable'
).
footable
();
$
(
'.footable'
).
footable
();
$
(
'.select2'
).
select2
({
$
(
'.select2'
).
select2
({
dropdownAutoWidth
:
true
,
dropdownAutoWidth
:
true
,
width
:
'auto'
width
:
'auto'
});
});
$
(
'#date .input-daterange'
).
datepicker
({
$
(
'#date .input-daterange'
).
datepicker
({
format
:
"yyyy-mm-dd"
,
format
:
"yyyy-mm-dd"
,
todayBtn
:
"linked"
,
todayBtn
:
"linked"
,
keyboardNavigation
:
false
,
keyboardNavigation
:
false
,
forceParse
:
false
,
forceParse
:
false
,
calendarWeeks
:
true
,
calendarWeeks
:
true
,
autoclose
:
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>
</script>
{% endblock %}
{% 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 = [
...
@@ -24,5 +24,6 @@ urlpatterns = [
# Command view
# Command view
url
(
r'^command/$'
,
views
.
CommandListView
.
as_view
(),
name
=
'command-list'
),
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 -*-
# -*- coding: utf-8 -*-
#
#
from
django.views.generic
import
ListView
from
django.views.generic
import
ListView
,
View
from
django.conf
import
settings
from
django.conf
import
settings
from
django.utils
import
timezone
from
django.utils.translation
import
ugettext
as
_
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.mixins
import
DatetimeSearchMixin
from
common.permissions
import
AdminUserRequiredMixin
from
common.permissions
import
AdminUserRequiredMixin
...
@@ -12,7 +14,7 @@ from ..models import Command
...
@@ -12,7 +14,7 @@ from ..models import Command
from
..
import
utils
from
..
import
utils
from
..backends
import
get_multi_command_storage
from
..backends
import
get_multi_command_storage
__all__
=
[
'CommandListView'
]
__all__
=
[
'CommandListView'
,
'CommandExportView'
]
common_storage
=
get_multi_command_storage
()
common_storage
=
get_multi_command_storage
()
...
@@ -61,7 +63,43 @@ class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
...
@@ -61,7 +63,43 @@ class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
return
super
()
.
get_context_data
(
**
kwargs
)
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):
...
@@ -101,7 +101,23 @@ class UserUpdatePKApi(generics.UpdateAPIView):
user
.
save
()
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
()
queryset
=
UserGroup
.
objects
.
all
()
serializer_class
=
UserGroupSerializer
serializer_class
=
UserGroupSerializer
permission_classes
=
(
IsOrgAdmin
,)
permission_classes
=
(
IsOrgAdmin
,)
...
@@ -203,13 +219,15 @@ class UserAuthApi(APIView):
...
@@ -203,13 +219,15 @@ class UserAuthApi(APIView):
permission_classes
=
(
AllowAny
,)
permission_classes
=
(
AllowAny
,)
serializer_class
=
UserSerializer
serializer_class
=
UserSerializer
key_prefix_limit
=
"_LOGIN_LIMIT_{}_{}"
key_prefix_limit
=
"_LOGIN_LIMIT_{}_{}"
key_prefix_block
=
"_LOGIN_BLOCK_{}"
def
post
(
self
,
request
):
def
post
(
self
,
request
):
# limit login
# limit login
username
=
request
.
data
.
get
(
'username'
)
username
=
request
.
data
.
get
(
'username'
)
ip
=
request
.
data
.
get
(
'remote_addr'
,
None
)
ip
=
request
.
data
.
get
(
'remote_addr'
,
None
)
ip
=
ip
if
ip
else
get_login_ip
(
request
)
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
):
if
is_block_login
(
key_limit
):
msg
=
_
(
"Log in frequently and try again later"
)
msg
=
_
(
"Log in frequently and try again later"
)
return
Response
({
'msg'
:
msg
},
status
=
401
)
return
Response
({
'msg'
:
msg
},
status
=
401
)
...
@@ -224,7 +242,7 @@ class UserAuthApi(APIView):
...
@@ -224,7 +242,7 @@ class UserAuthApi(APIView):
}
}
self
.
write_login_log
(
request
,
data
)
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
)
return
Response
({
'msg'
:
msg
},
status
=
401
)
if
not
user
.
otp_enabled
:
if
not
user
.
otp_enabled
:
...
...
apps/users/templates/users/user_detail.html
View file @
2ecfecb0
...
@@ -182,6 +182,14 @@
...
@@ -182,6 +182,14 @@
</span>
</span>
</td>
</td>
</tr>
</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>
</tbody>
</table>
</table>
</div>
</div>
...
@@ -275,7 +283,7 @@ $(document).ready(function() {
...
@@ -275,7 +283,7 @@ $(document).ready(function() {
.
on
(
'select2:unselect'
,
function
(
evt
)
{
.
on
(
'select2:unselect'
,
function
(
evt
)
{
var
data
=
evt
.
params
.
data
;
var
data
=
evt
.
params
.
data
;
delete
jumpserver
.
nodes_selected
[
data
.
id
];
delete
jumpserver
.
nodes_selected
[
data
.
id
];
})
})
;
})
})
.
on
(
'click'
,
'#is_active'
,
function
()
{
.
on
(
'click'
,
'#is_active'
,
function
()
{
var
the_url
=
"{% url 'api-users:user-detail' pk=user_object.id %}"
;
var
the_url
=
"{% url 'api-users:user-detail' pk=user_object.id %}"
;
...
@@ -293,7 +301,7 @@ $(document).ready(function() {
...
@@ -293,7 +301,7 @@ $(document).ready(function() {
.
on
(
'click'
,
'#force_enable_otp'
,
function
()
{
.
on
(
'click'
,
'#force_enable_otp'
,
function
()
{
{
%
if
request
.
user
==
user_object
%
}
{
%
if
request
.
user
==
user_object
%
}
toastr
.
error
(
"{% trans 'Goto profile page enable MFA' %}"
);
toastr
.
error
(
"{% trans 'Goto profile page enable MFA' %}"
);
return
return
;
{
%
endif
%
}
{
%
endif
%
}
var
the_url
=
"{% url 'api-users:user-detail' pk=user_object.id %}"
;
var
the_url
=
"{% url 'api-users:user-detail' pk=user_object.id %}"
;
...
@@ -426,6 +434,40 @@ $(document).ready(function() {
...
@@ -426,6 +434,40 @@ $(document).ready(function() {
var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'users:user-list' %}";
var redirect_url = "{% url 'users:user-list' %}";
objectDelete($this, name, the_url, redirect_url);
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>
</script>
{% endblock %}
{% endblock %}
apps/users/templates/users/user_list.html
View file @
2ecfecb0
...
@@ -59,7 +59,7 @@ function initTable() {
...
@@ -59,7 +59,7 @@ function initTable() {
ele
:
$
(
'#user_list_table'
),
ele
:
$
(
'#user_list_table'
),
columnDefs
:
[
columnDefs
:
[
{
targets
:
1
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
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
));
$
(
td
).
html
(
detail_btn
.
replace
(
"{{ DEFAULT_PK }}"
,
rowData
.
id
));
}},
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
...
...
apps/users/urls/api_urls.py
View file @
2ecfecb0
...
@@ -29,6 +29,8 @@ urlpatterns = [
...
@@ -29,6 +29,8 @@ urlpatterns = [
api
.
UserResetPKApi
.
as_view
(),
name
=
'user-public-key-reset'
),
api
.
UserResetPKApi
.
as_view
(),
name
=
'user-public-key-reset'
),
url
(
r'^users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/update/$'
,
url
(
r'^users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/update/$'
,
api
.
UserUpdatePKApi
.
as_view
(),
name
=
'user-public-key-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/$'
,
url
(
r'^users/(?P<pk>[0-9a-zA-Z\-]{36})/groups/$'
,
api
.
UserUpdateGroupApi
.
as_view
(),
name
=
'user-update-group'
),
api
.
UserUpdateGroupApi
.
as_view
(),
name
=
'user-update-group'
),
url
(
r'^groups/(?P<pk>[0-9a-zA-Z\-]{36})/users/$'
,
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'
...
@@ -8,13 +8,13 @@ app_name = 'users'
urlpatterns
=
[
urlpatterns
=
[
# Login view
# Login view
url
(
r'^login$'
,
views
.
UserLoginView
.
as_view
(),
name
=
'login'
),
url
(
r'^login
/
$'
,
views
.
UserLoginView
.
as_view
(),
name
=
'login'
),
url
(
r'^logout$'
,
views
.
UserLogoutView
.
as_view
(),
name
=
'logout'
),
url
(
r'^logout
/
$'
,
views
.
UserLogoutView
.
as_view
(),
name
=
'logout'
),
url
(
r'^login/otp$'
,
views
.
UserLoginOtpView
.
as_view
(),
name
=
'login-otp'
),
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
/
$'
,
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/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
/
$'
,
views
.
UserResetPasswordView
.
as_view
(),
name
=
'reset-password'
),
url
(
r'^password/reset/success$'
,
views
.
UserResetPasswordSuccessView
.
as_view
(),
name
=
'reset-password-success'
),
url
(
r'^password/reset/success
/
$'
,
views
.
UserResetPasswordSuccessView
.
as_view
(),
name
=
'reset-password-success'
),
# Profile
# Profile
url
(
r'^profile/$'
,
views
.
UserProfileView
.
as_view
(),
name
=
'user-profile'
),
url
(
r'^profile/$'
,
views
.
UserProfileView
.
as_view
(),
name
=
'user-profile'
),
...
@@ -29,23 +29,23 @@ urlpatterns = [
...
@@ -29,23 +29,23 @@ urlpatterns = [
url
(
r'^profile/otp/settings-success/$'
,
views
.
UserOtpSettingsSuccessView
.
as_view
(),
name
=
'user-otp-settings-success'
),
url
(
r'^profile/otp/settings-success/$'
,
views
.
UserOtpSettingsSuccessView
.
as_view
(),
name
=
'user-otp-settings-success'
),
# User view
# User view
url
(
r'^user$'
,
views
.
UserListView
.
as_view
(),
name
=
'user-list'
),
url
(
r'^user
/
$'
,
views
.
UserListView
.
as_view
(),
name
=
'user-list'
),
url
(
r'^user/export/'
,
views
.
UserExportView
.
as_view
(),
name
=
'user-export'
),
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'^first-login/$'
,
views
.
UserFirstLoginView
.
as_view
(),
name
=
'user-first-login'
),
url
(
r'^user/import/$'
,
views
.
UserBulkImportView
.
as_view
(),
name
=
'user-import'
),
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/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/(?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/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})
/
$'
,
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})/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/(?P<pk>[0-9a-zA-Z\-]{36})/login-history
/$
'
,
views
.
UserDetailView
.
as_view
(),
name
=
'user-login-history'
),
# User group view
# User group view
url
(
r'^user-group$'
,
views
.
UserGroupListView
.
as_view
(),
name
=
'user-group-list'
),
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/(?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/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})/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/(?P<pk>[0-9a-zA-Z\-]{36})/assets
/$
'
,
views
.
UserGroupGrantedAssetView
.
as_view
(),
name
=
'user-group-granted-asset'
),
# Login log
# Login log
url
(
r'^login-log/$'
,
views
.
LoginLogListView
.
as_view
(),
name
=
'login-log-list'
),
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):
...
@@ -212,10 +212,10 @@ def write_login_log(*args, **kwargs):
def
get_ip_city
(
ip
,
timeout
=
10
):
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
# 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
:
try
:
r
=
requests
.
get
(
url
,
timeout
=
timeout
)
r
=
requests
.
get
(
url
,
timeout
=
timeout
)
except
:
except
:
...
@@ -224,8 +224,8 @@ def get_ip_city(ip, timeout=10):
...
@@ -224,8 +224,8 @@ def get_ip_city(ip, timeout=10):
if
r
and
r
.
status_code
==
200
:
if
r
and
r
.
status_code
==
200
:
try
:
try
:
data
=
r
.
json
()
data
=
r
.
json
()
if
not
isinstance
(
data
,
int
)
and
data
[
'
ret'
]
==
1
:
if
not
isinstance
(
data
,
int
)
and
data
[
'
code'
]
==
0
:
city
=
data
[
'
country'
]
+
' '
+
data
[
'city'
]
city
=
data
[
'
data'
][
'country'
]
+
' '
+
data
[
'data'
]
[
'city'
]
except
ValueError
:
except
ValueError
:
pass
pass
return
city
return
city
...
@@ -333,7 +333,7 @@ def check_password_rules(password):
...
@@ -333,7 +333,7 @@ def check_password_rules(password):
return
bool
(
match_obj
)
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
=
cache
.
get
(
key_limit
)
count
=
count
+
1
if
count
else
1
count
=
count
+
1
if
count
else
1
...
@@ -343,6 +343,15 @@ def set_user_login_failed_count_to_cache(key_limit):
...
@@ -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
\
limit_time
=
setting_limit_time
.
cleaned_value
if
setting_limit_time
\
else
settings
.
DEFAULT_LOGIN_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
)
cache
.
set
(
key_limit
,
count
,
int
(
limit_time
)
*
60
)
...
@@ -357,3 +366,9 @@ def is_block_login(key_limit):
...
@@ -357,3 +366,9 @@ def is_block_login(key_limit):
if
count
and
count
>=
limit_count
:
if
count
and
count
>=
limit_count
:
return
True
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):
...
@@ -52,6 +52,7 @@ class UserLoginView(FormView):
redirect_field_name
=
'next'
redirect_field_name
=
'next'
key_prefix_captcha
=
"_LOGIN_INVALID_{}"
key_prefix_captcha
=
"_LOGIN_INVALID_{}"
key_prefix_limit
=
"_LOGIN_LIMIT_{}_{}"
key_prefix_limit
=
"_LOGIN_LIMIT_{}_{}"
key_prefix_block
=
"_LOGIN_BLOCK_{}"
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
if
request
.
user
.
is_staff
:
if
request
.
user
.
is_staff
:
...
@@ -65,7 +66,7 @@ class UserLoginView(FormView):
...
@@ -65,7 +66,7 @@ class UserLoginView(FormView):
# limit login authentication
# limit login authentication
ip
=
get_login_ip
(
request
)
ip
=
get_login_ip
(
request
)
username
=
self
.
request
.
POST
.
get
(
'username'
)
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
):
if
is_block_login
(
key_limit
):
return
self
.
render_to_response
(
self
.
get_context_data
(
block_login
=
True
))
return
self
.
render_to_response
(
self
.
get_context_data
(
block_login
=
True
))
...
@@ -91,8 +92,9 @@ class UserLoginView(FormView):
...
@@ -91,8 +92,9 @@ class UserLoginView(FormView):
# limit user login failed count
# limit user login failed count
ip
=
get_login_ip
(
self
.
request
)
ip
=
get_login_ip
(
self
.
request
)
key_limit
=
self
.
key_prefix_limit
.
format
(
ip
,
username
)
key_limit
=
self
.
key_prefix_limit
.
format
(
username
,
ip
)
set_user_login_failed_count_to_cache
(
key_limit
)
key_block
=
self
.
key_prefix_block
.
format
(
username
)
set_user_login_failed_count_to_cache
(
key_limit
,
key_block
)
# show captcha
# show captcha
cache
.
set
(
self
.
key_prefix_captcha
.
format
(
ip
),
1
,
3600
)
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
...
@@ -37,7 +37,9 @@ from common.models import Setting
from
common.permissions
import
AdminUserRequiredMixin
from
common.permissions
import
AdminUserRequiredMixin
from
..
import
forms
from
..
import
forms
from
..models
import
User
,
UserGroup
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
..signals
import
post_user_create
from
..tasks
import
write_login_log_async
from
..tasks
import
write_login_log_async
...
@@ -169,13 +171,17 @@ class UserDetailView(AdminUserRequiredMixin, DetailView):
...
@@ -169,13 +171,17 @@ class UserDetailView(AdminUserRequiredMixin, DetailView):
model
=
User
model
=
User
template_name
=
'users/user_detail.html'
template_name
=
'users/user_detail.html'
context_object_name
=
"user_object"
context_object_name
=
"user_object"
key_prefix_block
=
"_LOGIN_BLOCK_{}"
def
get_context_data
(
self
,
**
kwargs
):
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
())
groups
=
UserGroup
.
objects
.
exclude
(
id__in
=
self
.
object
.
groups
.
all
())
context
=
{
context
=
{
'app'
:
_
(
'Users'
),
'app'
:
_
(
'Users'
),
'action'
:
_
(
'User detail'
),
'action'
:
_
(
'User detail'
),
'groups'
:
groups
'groups'
:
groups
,
'unblock'
:
is_need_unblock
(
key_block
),
}
}
kwargs
.
update
(
context
)
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
docs/step_by_step.rst
View file @
2ecfecb0
...
@@ -228,7 +228,7 @@ Luna 已改为纯前端,需要 Nginx 来运行访问
...
@@ -228,7 +228,7 @@ Luna 已改为纯前端,需要 Nginx 来运行访问
-p 8081:8080 -v /opt/guacamole/key:/config/guacamole/key \
-p 8081:8080 -v /opt/guacamole/key:/config/guacamole/key \
-e JUMPSERVER_KEY_DIR=/config/guacamole/key \
-e JUMPSERVER_KEY_DIR=/config/guacamole/key \
-e JUMPSERVER_SERVER=http://<填写本机的IP地址>:8080 \
-e JUMPSERVER_SERVER=http://<填写本机的IP地址>:8080 \
registry.jumpserver.org/public/guacamole:
1.0.0
registry.jumpserver.org/public/guacamole:
latest
这里所需要注意的是 guacamole 暴露出来的端口是 8081,若与主机上其他端口冲突请自定义一下。
这里所需要注意的是 guacamole 暴露出来的端口是 8081,若与主机上其他端口冲突请自定义一下。
...
...
jms
View file @
2ecfecb0
...
@@ -122,8 +122,8 @@ def start_gunicorn():
...
@@ -122,8 +122,8 @@ def start_gunicorn():
cmd
=
[
cmd
=
[
'gunicorn'
,
'jumpserver.wsgi'
,
'gunicorn'
,
'jumpserver.wsgi'
,
'-b'
,
bind
,
'-b'
,
bind
,
'-w'
,
str
(
WORKERS
),
'-k'
,
'eventlet'
,
'-k'
,
'eventlet'
,
'-w'
,
str
(
WORKERS
),
'--access-logformat'
,
log_format
,
'--access-logformat'
,
log_format
,
'-p'
,
pid_file
,
'-p'
,
pid_file
,
]
]
...
...
requirements/requirements.txt
View file @
2ecfecb0
...
@@ -48,7 +48,7 @@ MarkupSafe==1.0
...
@@ -48,7 +48,7 @@ MarkupSafe==1.0
mysqlclient==1.3.12
mysqlclient==1.3.12
olefile==0.44
olefile==0.44
openapi-codec==1.3.2
openapi-codec==1.3.2
paramiko==2.4.
0
paramiko==2.4.
1
passlib==1.7.1
passlib==1.7.1
Pillow==4.3.0
Pillow==4.3.0
pyasn1==0.4.2
pyasn1==0.4.2
...
...
utils/make_migrations.sh
View file @
2ecfecb0
...
@@ -5,4 +5,4 @@ python3 ../apps/manage.py makemigrations
...
@@ -5,4 +5,4 @@ python3 ../apps/manage.py makemigrations
python3 ../apps/manage.py migrate
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