Merge pull request #2642 from jumpserver/dev

Dev
pull/2684/head 1.4.10
老广 2019-04-30 10:46:28 +08:00 committed by GitHub
commit a025930957
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 903 additions and 338 deletions

View File

@ -201,8 +201,7 @@ Jumpserver 采纳分布式架构,支持多机房跨区域部署,中心节点
### License & Copyright ### License & Copyright
---- Copyright (c) 2014-2019 飞致云 FIT2CLOUD, All rights reserved.
Copyright (c) 2014-2019 Beijing Duizhan Tech, Inc., All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

View File

@ -1,19 +1,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid
import random import random
from rest_framework import generics from rest_framework import generics
from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.core.cache import cache
from django.db.models import Q from django.db.models import Q
from common.mixins import IDInFilterMixin from common.mixins import IDInFilterMixin
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from ..models import Asset, AdminUser, Node from ..models import Asset, AdminUser, Node
from .. import serializers from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \ from ..tasks import update_asset_hardware_info_manual, \
@ -25,7 +31,7 @@ logger = get_logger(__file__)
__all__ = [ __all__ = [
'AssetViewSet', 'AssetListUpdateApi', 'AssetViewSet', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi', 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi' 'AssetGatewayApi', 'AssetBulkUpdateSelectAPI'
] ]
@ -92,6 +98,21 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
class AssetBulkUpdateSelectAPI(APIView):
permission_classes = (IsOrgAdmin,)
def post(self, request, *args, **kwargs):
assets_id = request.data.get('assets_id', '')
if assets_id:
spm = uuid.uuid4().hex
key = CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)
cache.set(key, assets_id, 300)
url = reverse_lazy('assets:asset-bulk-update') + '?spm=%s' % spm
return Response({'url': url})
error = _('Please select assets that need to be updated')
return Response({'error': error}, status=400)
class AssetRefreshHardwareApi(generics.RetrieveAPIView): class AssetRefreshHardwareApi(generics.RetrieveAPIView):
""" """
Refresh asset hardware info Refresh asset hardware info

View File

@ -48,3 +48,6 @@ TASK_OPTIONS = {
'timeout': 10, 'timeout': 10,
'forks': 10, 'forks': 10,
} }
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'

View File

@ -3,6 +3,8 @@
from django.core.cache import cache from django.core.cache import cache
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from ..models import Node, AdminUser from ..models import Node, AdminUser
from ..const import ADMIN_USER_CONN_CACHE_KEY from ..const import ADMIN_USER_CONN_CACHE_KEY
@ -18,6 +20,7 @@ class AdminUserSerializer(serializers.ModelSerializer):
reachable_amount = serializers.SerializerMethodField() reachable_amount = serializers.SerializerMethodField()
class Meta: class Meta:
list_serializer_class = AdaptedBulkListSerializer
model = AdminUser model = AdminUser
fields = '__all__' fields = '__all__'

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import serializers from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import Asset from ..models import Asset
from .system_user import AssetSystemUserSerializer from .system_user import AssetSystemUserSerializer
@ -19,7 +19,7 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
""" """
class Meta: class Meta:
model = Asset model = Asset
list_serializer_class = BulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = '__all__' fields = '__all__'
validators = [] validators = []

View File

@ -3,6 +3,7 @@
from rest_framework import serializers from rest_framework import serializers
from common.fields import ChoiceDisplayField from common.fields import ChoiceDisplayField
from common.serializers import AdaptedBulkListSerializer
from ..models import CommandFilter, CommandFilterRule, SystemUser from ..models import CommandFilter, CommandFilterRule, SystemUser
@ -12,6 +13,7 @@ class CommandFilterSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = CommandFilter model = CommandFilter
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__' fields = '__all__'
@ -21,3 +23,4 @@ class CommandFilterRuleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = CommandFilterRule model = CommandFilterRule
fields = '__all__' fields = '__all__'
list_serializer_class = AdaptedBulkListSerializer

View File

@ -2,6 +2,8 @@
# #
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from ..models import Domain, Gateway from ..models import Domain, Gateway
@ -12,6 +14,7 @@ class DomainSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Domain model = Domain
fields = '__all__' fields = '__all__'
list_serializer_class = AdaptedBulkListSerializer
@staticmethod @staticmethod
def get_asset_count(obj): def get_asset_count(obj):
@ -25,6 +28,7 @@ class DomainSerializer(serializers.ModelSerializer):
class GatewaySerializer(serializers.ModelSerializer): class GatewaySerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Gateway model = Gateway
list_serializer_class = AdaptedBulkListSerializer
fields = [ fields = [
'id', 'name', 'ip', 'port', 'protocol', 'username', 'id', 'name', 'ip', 'port', 'protocol', 'username',
'domain', 'is_active', 'date_created', 'date_updated', 'domain', 'is_active', 'date_created', 'date_updated',

View File

@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import serializers from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.serializers import AdaptedBulkListSerializer
from ..models import Label from ..models import Label
@ -12,7 +13,7 @@ class LabelSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Label model = Label
fields = '__all__' fields = '__all__'
list_serializer_class = BulkListSerializer list_serializer_class = AdaptedBulkListSerializer
@staticmethod @staticmethod
def get_asset_count(obj): def get_asset_count(obj):

View File

@ -1,5 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from ..models import SystemUser, Asset from ..models import SystemUser, Asset
from .base import AuthSerializer from .base import AuthSerializer
@ -17,6 +19,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = SystemUser model = SystemUser
exclude = ('_password', '_private_key', '_public_key') exclude = ('_password', '_private_key', '_public_key')
list_serializer_class = AdaptedBulkListSerializer
def get_field_names(self, declared_fields, info): def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
@ -61,13 +64,19 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
""" """
查看授权的资产系统用户的数据结构这个和AssetSerializer不同字段少 查看授权的资产系统用户的数据结构这个和AssetSerializer不同字段少
""" """
actions = serializers.SerializerMethodField()
class Meta: class Meta:
model = SystemUser model = SystemUser
fields = ( fields = (
'id', 'name', 'username', 'priority', 'id', 'name', 'username', 'priority',
'protocol', 'comment', 'login_mode' 'protocol', 'comment', 'login_mode', 'actions',
) )
@staticmethod
def get_actions(obj):
return [action.name for action in obj.actions]
class SystemUserSimpleSerializer(serializers.ModelSerializer): class SystemUserSimpleSerializer(serializers.ModelSerializer):
""" """

View File

@ -98,6 +98,7 @@ function initTable() {
order: [], order: [],
columnDefs: [ columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) { {targets: 0, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},

View File

@ -44,9 +44,10 @@ $(document).ready(function(){
var options = { var options = {
ele: $('#admin_user_list_table'), ele: $('#admin_user_list_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, render: function (cellData, tp, rowData, meta) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id);
}}, }},
{targets: 4, createdCell: function (td, cellData) { {targets: 4, createdCell: function (td, cellData) {
var innerHtml = ""; var innerHtml = "";
@ -82,7 +83,6 @@ $(document).ready(function(){
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>"; innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
} }
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}}, }},
{targets: 8, createdCell: function (td, cellData, rowData) { {targets: 8, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
@ -90,8 +90,8 @@ $(document).ready(function(){
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
}}], }}],
ajax_url: '{% url "api-assets:admin-user-list" %}', ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" }, columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }] {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}, {data: "id"}]
}; };
jumpserver.initServerSideDataTable(options) jumpserver.initServerSideDataTable(options)
}) })

View File

@ -156,6 +156,7 @@ function initTable() {
ele: $('#asset_list_table'), ele: $('#asset_list_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %} {% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>'; var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
@ -657,9 +658,23 @@ $(document).ready(function(){
}); });
} }
function doUpdate() { function doUpdate() {
var id_list_string = id_list.join(','); var data = {
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string; 'assets_id':id_list
location.href = url };
function error(data) {
toastr.error(JSON.parse(data).error)
}
function success(data) {
location.href = data.url;
}
APIUpdateAttr({
'url': "{% url 'api-assets:asset-bulk-update-select' %}",
'method': 'POST',
'body': JSON.stringify(data),
'flash_message': false,
'success': success,
'error': error,
})
} }
function doRemove() { function doRemove() {

View File

@ -40,6 +40,7 @@ function initTable() {
ele: $('#cmd_filter_list_table'), ele: $('#cmd_filter_list_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url 'assets:cmd-filter-detail' pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url 'assets:cmd-filter-detail' pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},

View File

@ -41,6 +41,7 @@ function initTable() {
ele: $('#domain_list_table'), ele: $('#domain_list_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:domain-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:domain-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},

View File

@ -30,6 +30,7 @@ function initTable() {
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
{# var detail_btn = '<a href="{% url "assets:label-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';#} {# var detail_btn = '<a href="{% url "assets:label-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';#}
cellData = htmlEscape(cellData);
var detail_btn = '<a>' + cellData + '</a>'; var detail_btn = '<a>' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},

View File

@ -144,6 +144,7 @@ function initAssetsTable() {
order: [], order: [],
columnDefs: [ columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) { {targets: 0, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},

View File

@ -49,6 +49,7 @@ function initTable() {
ele: $('#system_user_list_table'), ele: $('#system_user_list_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:system-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));
}}, }},

View File

@ -25,6 +25,8 @@ cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-r
urlpatterns = [ urlpatterns = [
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
path('asset/update/select/',
api.AssetBulkUpdateSelectAPI.as_view(), name='asset-bulk-update-select'),
path('assets/<uuid:pk>/refresh/', path('assets/<uuid:pk>/refresh/',
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'), api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
path('assets/<uuid:pk>/alive/', path('assets/<uuid:pk>/alive/',

View File

@ -28,6 +28,7 @@ from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none, get_logger from common.utils import get_object_or_none, get_logger
from common.permissions import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from orgs.utils import current_org from orgs.utils import current_org
from .. import forms from .. import forms
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
@ -120,15 +121,12 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
form = None form = None
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
assets_id = self.request.GET.get('assets_id', '') spm = request.GET.get('spm', '')
self.id_list = [i for i in assets_id.split(',')] assets_id = cache.get(CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm))
if kwargs.get('form'): if kwargs.get('form'):
self.form = kwargs['form'] self.form = kwargs['form']
elif assets_id: elif assets_id:
self.form = self.form_class( self.form = self.form_class(initial={'assets': assets_id})
initial={'assets': self.id_list}
)
else: else:
self.form = self.form_class() self.form = self.form_class()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View File

@ -23,15 +23,15 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request): def process_request(self, request):
# Don't need openid auth if AUTH_OPENID is False # Don't need openid auth if AUTH_OPENID is False
if not settings.AUTH_OPENID: if not settings.AUTH_OPENID:
logger.info("Not settings.AUTH_OPENID") logger.debug("Not settings.AUTH_OPENID")
return return
# Don't need check single logout if user not authenticated # Don't need check single logout if user not authenticated
if not request.user.is_authenticated: if not request.user.is_authenticated:
logger.info("User is not authenticated") logger.debug("User is not authenticated")
return return
elif not request.session[BACKEND_SESSION_KEY].endswith( elif not request.session[BACKEND_SESSION_KEY].endswith(
BACKEND_OPENID_AUTH_CODE): BACKEND_OPENID_AUTH_CODE):
logger.info("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE") logger.debug("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE")
return return
# Check openid user single logout or not with access_token # Check openid user single logout or not with access_token
@ -40,7 +40,6 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin):
client.openid_connect_client.userinfo( client.openid_connect_client.userinfo(
token=request.session.get(OIDT_ACCESS_TOKEN) token=request.session.get(OIDT_ACCESS_TOKEN)
) )
except Exception as e: except Exception as e:
logout(request) logout(request)
logger.error(e) logger.error(e)

View File

@ -14,6 +14,14 @@ class UserLoginForm(AuthenticationForm):
max_length=128, strip=False max_length=128, strip=False
) )
error_messages = {
'invalid_login': _(
"Please enter a correct username and password. Note that both "
"fields may be case-sensitive."
),
'inactive': _("This account is inactive."),
}
def confirm_login_allowed(self, user): def confirm_login_allowed(self, user):
if not user.is_staff: if not user.is_staff:
raise forms.ValidationError( raise forms.ValidationError(

View File

@ -52,7 +52,7 @@
</style> </style>
</head> </head>
<body style="height: 100%"> <body style="height: 100%;font-size: 13px">
<div> <div>
<div class="box-1"> <div class="box-1">
<div class="box-2"> <div class="box-2">
@ -60,19 +60,19 @@
</div> </div>
<div class="box-3"> <div class="box-3">
<div style="background-color: white"> <div style="background-color: white">
<div style="margin-top: 40px;padding-top: 50px;"> <div style="margin-top: 30px;padding-top: 40px;padding-left: 20px;padding-right: 20px;height: 80px">
<span style="font-size: 24px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span> <span style="font-size: 21px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span>
</div> </div>
<div style="font-size: 12px;color: #999999;letter-spacing: 0;line-height: 18px;margin-top: 10px"> <div style="font-size: 12px;color: #999999;letter-spacing: 0;line-height: 18px;margin-top: 18px">
{% trans 'Welcome back, please enter username and password to login' %} {% trans 'Welcome back, please enter username and password to login' %}
</div> </div>
<div style="margin-bottom: 10px"> <div style="margin-bottom: 10px">
<div> <div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<div class="contact-form col-md-10" style="margin-top: 20px;height: 35px"> <div class="contact-form col-md-10" style="margin-top: 10px;height: 35px">
<form id="contact-form" action="" method="post" role="form" novalidate="novalidate"> <form id="contact-form" action="" method="post" role="form" novalidate="novalidate">
{% csrf_token %} {% csrf_token %}
<div style="height: 48px;color: red"> <div style="height: 45px;color: red;line-height: 17px;">
{% if block_login %} {% if block_login %}
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p> <p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
{% elif password_expired %} {% elif password_expired %}
@ -92,7 +92,7 @@
<div class="form-group"> <div class="form-group">
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required=""> <input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
</div> </div>
<div class="form-group" style="height: 50px;margin-bottom: 0"> <div class="form-group" style="height: 50px;margin-bottom: 0;font-size: 13px">
{{ form.captcha }} {{ form.captcha }}
</div> </div>
<div class="form-group" style="margin-top: 10px"> <div class="form-group" style="margin-top: 10px">

View File

@ -59,6 +59,11 @@ class UserLoginView(FormView):
return redirect(redirect_user_first_login_or_index( return redirect(redirect_user_first_login_or_index(
request, self.redirect_field_name) request, self.redirect_field_name)
) )
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
if settings.AUTH_OPENID and not self.request.GET.get('admin', 0):
query_string = request.GET.urlencode()
login_url = "{}?{}".format(settings.LOGIN_URL, query_string)
return redirect(login_url)
request.session.set_test_cookie() request.session.set_test_cookie()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View File

@ -3,7 +3,7 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
create_success_msg = _("<b>%(name)s</b> was created successfully") create_success_msg = _("%(name)s was created successfully")
update_success_msg = _("<b>%(name)s</b> was updated successfully") update_success_msg = _("%(name)s was updated successfully")
FILE_END_GUARD = ">>> Content End <<<" FILE_END_GUARD = ">>> Content End <<<"
celery_task_pre_key = "CELERY_" celery_task_pre_key = "CELERY_"

View File

@ -4,6 +4,10 @@ from django.db import models
from django.http import JsonResponse from django.http import JsonResponse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.utils import html
from rest_framework.settings import api_settings
from rest_framework.exceptions import ValidationError
from rest_framework.fields import SkipField
class NoDeleteQuerySet(models.query.QuerySet): class NoDeleteQuerySet(models.query.QuerySet):
@ -89,6 +93,60 @@ class BulkSerializerMixin(object):
return ret return ret
class BulkListSerializerMixin(object):
"""
Become rest_framework_bulk doing bulk update raise Exception:
'QuerySet' object has no attribute 'pk' when doing bulk update
so rewrite it .
https://github.com/miki725/django-rest-framework-bulk/issues/68
"""
def to_internal_value(self, data):
"""
List of dicts of native values <- List of dicts of primitive datatypes.
"""
if html.is_html_input(data):
data = html.parse_html_list(data)
if not isinstance(data, list):
message = self.error_messages['not_a_list'].format(
input_type=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='not_a_list')
if not self.allow_empty and len(data) == 0:
if self.parent and self.partial:
raise SkipField()
message = self.error_messages['empty']
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='empty')
ret = []
errors = []
for item in data:
try:
# prepare child serializer to only handle one instance
self.child.instance = self.instance.get(id=item['id']) if self.instance else None
self.child.initial_data = item
# raw
validated = self.child.run_validation(item)
except ValidationError as exc:
errors.append(exc.detail)
else:
ret.append(validated)
errors.append({})
if any(errors):
raise ValidationError(errors)
return ret
class DatetimeSearchMixin: class DatetimeSearchMixin:
date_format = '%Y-%m-%d' date_format = '%Y-%m-%d'
date_from = date_to = None date_from = date_to = None

View File

@ -35,7 +35,7 @@ class IsSuperUser(IsValidUser):
class IsSuperUserOrAppUser(IsSuperUser): class IsSuperUserOrAppUser(IsSuperUser):
def has_permission(self, request, view): def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \ return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app) or request.user.is_app
class IsOrgAdmin(IsValidUser): class IsOrgAdmin(IsValidUser):

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
#
from .mixins import BulkListSerializerMixin
from rest_framework_bulk.serializers import BulkListSerializer
class AdaptedBulkListSerializer(BulkListSerializerMixin, BulkListSerializer):
pass

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
VERSION = '1.4.9' VERSION = '1.4.10'

View File

@ -15,7 +15,7 @@ def jumpserver_processor(request):
'FAVICON_URL': static('img/facio.ico'), 'FAVICON_URL': static('img/facio.ico'),
'JMS_TITLE': 'Jumpserver', 'JMS_TITLE': 'Jumpserver',
'VERSION': settings.VERSION, 'VERSION': settings.VERSION,
'COPYRIGHT': _('Beijing Duizhan Tech, Inc.') + ' © 2014-2019' 'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2019'
} }
return context return context

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -82,6 +82,7 @@
<script> <script>
var zTree, show = 0; var zTree, show = 0;
var systemUserId = null; var systemUserId = null;
var url = null;
var treeUrl = "{% url 'api-perms:my-nodes-assets-as-tree' %}?cache_policy=1"; var treeUrl = "{% url 'api-perms:my-nodes-assets-as-tree' %}?cache_policy=1";
function initTree() { function initTree() {
@ -114,6 +115,9 @@ function initTree() {
if (systemUserId) { if (systemUserId) {
url = treeUrl + '&system_user=' + systemUserId url = treeUrl + '&system_user=' + systemUserId
} }
else{
url = treeUrl
}
$.get(url, function(data, status){ $.get(url, function(data, status){
$.fn.zTree.init($("#assetTree"), setting, data); $.fn.zTree.init($("#assetTree"), setting, data);

View File

@ -1,11 +1,11 @@
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework import serializers from rest_framework import serializers
from rest_framework_bulk import BulkListSerializer
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import Asset, Domain, AdminUser, SystemUser, Label from assets.models import Asset, Domain, AdminUser, SystemUser, Label
from perms.models import AssetPermission from perms.models import AssetPermission
from common.serializers import AdaptedBulkListSerializer
from .utils import set_current_org, get_current_org from .utils import set_current_org, get_current_org
from .models import Organization from .models import Organization
from .mixins import OrgMembershipSerializerMixin from .mixins import OrgMembershipSerializerMixin
@ -14,7 +14,7 @@ from .mixins import OrgMembershipSerializerMixin
class OrgSerializer(ModelSerializer): class OrgSerializer(ModelSerializer):
class Meta: class Meta:
model = Organization model = Organization
list_serializer_class = BulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = '__all__' fields = '__all__'
read_only_fields = ['created_by', 'date_created'] read_only_fields = ['created_by', 'date_created']
@ -70,12 +70,12 @@ class OrgReadSerializer(ModelSerializer):
class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer): class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer):
class Meta: class Meta:
model = Organization.admins.through model = Organization.admins.through
list_serializer_class = BulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = '__all__' fields = '__all__'
class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer): class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer):
class Meta: class Meta:
model = Organization.users.through model = Organization.users.through
list_serializer_class = BulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = '__all__' fields = '__all__'

View File

@ -10,7 +10,7 @@ from rest_framework.pagination import LimitOffsetPagination
from common.permissions import IsOrgAdmin from common.permissions import IsOrgAdmin
from common.utils import get_object_or_none from common.utils import get_object_or_none
from ..models import AssetPermission from ..models import AssetPermission, Action
from ..hands import ( from ..hands import (
User, UserGroup, Asset, Node, SystemUser, User, UserGroup, Asset, Node, SystemUser,
) )
@ -20,10 +20,16 @@ from .. import serializers
__all__ = [ __all__ = [
'AssetPermissionViewSet', 'AssetPermissionRemoveUserApi', 'AssetPermissionViewSet', 'AssetPermissionRemoveUserApi',
'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi', 'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi',
'AssetPermissionAddAssetApi', 'AssetPermissionAddAssetApi', 'ActionViewSet',
] ]
class ActionViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Action.objects.all()
serializer_class = serializers.ActionSerializer
permission_classes = (IsOrgAdmin,)
class AssetPermissionViewSet(viewsets.ModelViewSet): class AssetPermissionViewSet(viewsets.ModelViewSet):
""" """
资产授权列表的增删改查api 资产授权列表的增删改查api

View File

@ -16,7 +16,8 @@ from common.tree import TreeNodeSerializer
from common.utils import get_logger from common.utils import get_logger
from orgs.utils import set_to_root_org from orgs.utils import set_to_root_org
from ..utils import ( from ..utils import (
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
check_system_user_action
) )
from ..hands import ( from ..hands import (
AssetGrantedSerializer, User, Asset, Node, AssetGrantedSerializer, User, Asset, Node,
@ -24,6 +25,7 @@ from ..hands import (
) )
from .. import serializers from .. import serializers
from ..mixins import AssetsFilterMixin from ..mixins import AssetsFilterMixin
from ..models import Action
logger = get_logger(__name__) logger = get_logger(__name__)
@ -31,7 +33,7 @@ __all__ = [
'UserGrantedAssetsApi', 'UserGrantedNodesApi', 'UserGrantedAssetsApi', 'UserGrantedNodesApi',
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi', 'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi', 'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi',
'UserGrantedNodesWithAssetsAsTreeApi', 'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
] ]
@ -403,16 +405,45 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView):
user_id = request.query_params.get('user_id', '') user_id = request.query_params.get('user_id', '')
asset_id = request.query_params.get('asset_id', '') asset_id = request.query_params.get('asset_id', '')
system_id = request.query_params.get('system_user_id', '') system_id = request.query_params.get('system_user_id', '')
action_name = request.query_params.get('action_name', '')
user = get_object_or_404(User, id=user_id) user = get_object_or_404(User, id=user_id)
asset = get_object_or_404(Asset, id=asset_id) asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_id) su = get_object_or_404(SystemUser, id=system_id)
action = get_object_or_404(Action, name=action_name)
util = AssetPermissionUtil(user, cache_policy=self.cache_policy) util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
assets_granted = util.get_assets() granted_assets = util.get_assets()
if system_user in assets_granted.get(asset, []): granted_system_users = granted_assets.get(asset, [])
return Response({'msg': True}, status=200)
else: if su not in granted_system_users:
return Response({'msg': False}, status=403) return Response({'msg': False}, status=403)
_su = next((s for s in granted_system_users if s.id == su.id), None)
if not check_system_user_action(_su, action):
return Response({'msg': False}, status=403)
return Response({'msg': True}, status=200)
class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView):
permission_classes = (IsOrgAdminOrAppUser,)
def get(self, request, *args, **kwargs):
user_id = request.query_params.get('user_id', '')
asset_id = request.query_params.get('asset_id', '')
system_id = request.query_params.get('system_user_id', '')
user = get_object_or_404(User, id=user_id)
asset = get_object_or_404(Asset, id=asset_id)
su = get_object_or_404(SystemUser, id=system_id)
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
granted_assets = util.get_assets()
granted_system_users = granted_assets.get(asset, [])
_su = next((s for s in granted_system_users if s.id == su.id), None)
if not _su:
return Response({'actions': []}, status=403)
actions = [action.name for action in getattr(_su, 'actions', [])]
return Response({'actions': actions}, status=200)

22
apps/perms/const.py Normal file
View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
__all__ = [
'PERMS_ACTION_NAME_ALL', 'PERMS_ACTION_NAME_CONNECT',
'PERMS_ACTION_NAME_DOWNLOAD_FILE', 'PERMS_ACTION_NAME_UPLOAD_FILE',
'PERMS_ACTION_NAME_CHOICES'
]
PERMS_ACTION_NAME_ALL = 'all'
PERMS_ACTION_NAME_CONNECT = 'connect'
PERMS_ACTION_NAME_UPLOAD_FILE = 'upload_file'
PERMS_ACTION_NAME_DOWNLOAD_FILE = 'download_file'
PERMS_ACTION_NAME_CHOICES = (
(PERMS_ACTION_NAME_ALL, _('All')),
(PERMS_ACTION_NAME_CONNECT, _('Connect')),
(PERMS_ACTION_NAME_UPLOAD_FILE, _('Upload file')),
(PERMS_ACTION_NAME_DOWNLOAD_FILE, _('Download file')),
)

View File

@ -47,10 +47,17 @@ class AssetPermissionForm(OrgModelForm):
'system_users': forms.SelectMultiple( 'system_users': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('System user')} attrs={'class': 'select2', 'data-placeholder': _('System user')}
), ),
'actions': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Action')}
)
} }
labels = { labels = {
'nodes': _("Node"), 'nodes': _("Node"),
} }
help_texts = {
'actions': _('Tips: The RDP protocol does not support separate '
'controls for uploading or downloading files')
}
def clean_user_groups(self): def clean_user_groups(self):
users = self.cleaned_data.get('users') users = self.cleaned_data.get('users')

View File

@ -0,0 +1,33 @@
# Generated by Django 2.1.7 on 2019-04-12 07:00
from django.db import migrations, models
import uuid
def add_default_actions(apps, schema_editor):
from ..const import PERMS_ACTION_NAME_CHOICES
action_model = apps.get_model('perms', 'Action')
db_alias = schema_editor.connection.alias
for action, _ in PERMS_ACTION_NAME_CHOICES:
action_model.objects.using(db_alias).update_or_create(name=action)
class Migration(migrations.Migration):
dependencies = [
('perms', '0002_auto_20171228_0025_squashed_0009_auto_20180903_1132'),
]
operations = [
migrations.CreateModel(
name='Action',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(choices=[('all', 'All'), ('connect', 'Connect'), ('upload_file', 'Upload file'), ('download_file', 'Download file')], max_length=128, unique=True, verbose_name='Name')),
],
options={
'verbose_name': 'Action',
},
),
migrations.RunPython(add_default_actions)
]

View File

@ -0,0 +1,31 @@
# Generated by Django 2.1.7 on 2019-04-12 09:17
from django.db import migrations, models
def set_default_action_to_existing_perms(apps, schema_editor):
from orgs.utils import set_to_root_org
from ..models import Action
set_to_root_org()
perm_model = apps.get_model('perms', 'AssetPermission')
db_alias = schema_editor.connection.alias
perms = perm_model.objects.using(db_alias).all()
default_action = Action.get_action_all()
for perm in perms:
perm.actions.add(default_action.id)
class Migration(migrations.Migration):
dependencies = [
('perms', '0003_action'),
]
operations = [
migrations.AddField(
model_name='assetpermission',
name='actions',
field=models.ManyToManyField(blank=True, related_name='permissions', to='perms.Action', verbose_name='Action'),
),
migrations.RunPython(set_default_action_to_existing_perms)
]

View File

@ -7,6 +7,26 @@ from django.utils import timezone
from common.utils import date_expired_default, set_or_append_attr_bulk from common.utils import date_expired_default, set_or_append_attr_bulk
from orgs.mixins import OrgModelMixin, OrgManager from orgs.mixins import OrgModelMixin, OrgManager
from .const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL
class Action(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(
max_length=128, unique=True, choices=PERMS_ACTION_NAME_CHOICES,
verbose_name=_('Name')
)
class Meta:
verbose_name = _('Action')
def __str__(self):
return self.get_name_display()
@classmethod
def get_action_all(cls):
return cls.objects.get(name=PERMS_ACTION_NAME_ALL)
class AssetPermissionQuerySet(models.QuerySet): class AssetPermissionQuerySet(models.QuerySet):
def active(self): def active(self):
@ -30,6 +50,7 @@ class AssetPermission(OrgModelMixin):
assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user")) system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
actions = models.ManyToManyField('Action', related_name='permissions', blank=True, verbose_name=_('Action'))
is_active = models.BooleanField(default=True, verbose_name=_('Active')) is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired'))

View File

@ -4,7 +4,7 @@
from rest_framework import serializers from rest_framework import serializers
from common.fields import StringManyToManyField from common.fields import StringManyToManyField
from .models import AssetPermission from .models import AssetPermission, Action
from assets.models import Node, Asset, SystemUser from assets.models import Node, Asset, SystemUser
from assets.serializers import AssetGrantedSerializer from assets.serializers import AssetGrantedSerializer
@ -13,9 +13,16 @@ __all__ = [
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer', 'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer',
'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', 'AssetPermissionNodeSerializer', 'GrantedNodeSerializer',
'GrantedAssetSerializer', 'GrantedSystemUserSerializer', 'GrantedAssetSerializer', 'GrantedSystemUserSerializer',
'ActionSerializer',
] ]
class ActionSerializer(serializers.ModelSerializer):
class Meta:
model = Action
fields = '__all__'
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer): class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AssetPermission model = AssetPermission
@ -28,6 +35,7 @@ class AssetPermissionListSerializer(serializers.ModelSerializer):
assets = StringManyToManyField(many=True, read_only=True) assets = StringManyToManyField(many=True, read_only=True)
nodes = StringManyToManyField(many=True, read_only=True) nodes = StringManyToManyField(many=True, read_only=True)
system_users = StringManyToManyField(many=True, read_only=True) system_users = StringManyToManyField(many=True, read_only=True)
actions = StringManyToManyField(many=True, read_only=True)
is_valid = serializers.BooleanField() is_valid = serializers.BooleanField()
is_expired = serializers.BooleanField() is_expired = serializers.BooleanField()

View File

@ -2,15 +2,37 @@
# #
from django.db.models.signals import m2m_changed, post_save, post_delete from django.db.models.signals import m2m_changed, post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from django.db import transaction
from common.utils import get_logger from common.utils import get_logger
from .utils import AssetPermissionUtil from .utils import AssetPermissionUtil
from .models import AssetPermission from .models import AssetPermission, Action
logger = get_logger(__file__) logger = get_logger(__file__)
def on_transaction_commit(func):
"""
如果不调用on_commit, 对象创建时添加多对多字段值失败
"""
def inner(*args, **kwargs):
transaction.on_commit(lambda: func(*args, **kwargs))
return inner
@receiver(post_save, sender=AssetPermission, dispatch_uid="my_unique_identifier")
@on_transaction_commit
def on_permission_created(sender, instance=None, created=False, **kwargs):
actions = instance.actions.all()
if created and not actions:
default_action = Action.get_action_all()
instance.actions.add(default_action)
logger.debug(
"Set default action to perms: {}".format(default_action, instance)
)
@receiver(post_save, sender=AssetPermission) @receiver(post_save, sender=AssetPermission)
def on_permission_update(sender, **kwargs): def on_permission_update(sender, **kwargs):
AssetPermissionUtil.expire_all_cache() AssetPermissionUtil.expire_all_cache()

View File

@ -47,6 +47,9 @@
{% bootstrap_field form.nodes layout="horizontal" %} {% bootstrap_field form.nodes layout="horizontal" %}
{% bootstrap_field form.system_users layout="horizontal" %} {% bootstrap_field form.system_users layout="horizontal" %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans 'Action' %}</h3>
{% bootstrap_field form.actions layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3> <h3>{% trans 'Other' %}</h3>
<div class="form-group"> <div class="form-group">
<label for="{{ form.is_active.id_for_label }}" class="col-sm-2 control-label">{% trans 'Active' %}</label> <label for="{{ form.is_active.id_for_label }}" class="col-sm-2 control-label">{% trans 'Active' %}</label>

View File

@ -130,6 +130,9 @@ function format(d) {
if (d.system_users.length > 0) { if (d.system_users.length > 0) {
data += makeLabel(["{% trans 'System user' %}", d.system_users.join(", ")]) data += makeLabel(["{% trans 'System user' %}", d.system_users.join(", ")])
} }
if (d.actions.length > 0) {
data += makeLabel(["{% trans 'Action' %}", d.actions.join(", ")])
}
return data return data
} }
@ -143,6 +146,7 @@ function initTable() {
$(td).html("<i class='fa fa-angle-right'></i>"); $(td).html("<i class='fa fa-angle-right'></i>");
}}, }},
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "perms:asset-permission-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "perms:asset-permission-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},

View File

@ -7,6 +7,7 @@ from .. import api
app_name = 'perms' app_name = 'perms'
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register('actions', api.ActionViewSet, 'action')
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission') router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
urlpatterns = [ urlpatterns = [
@ -67,6 +68,8 @@ urlpatterns = [
# 验证用户是否有某个资产和系统用户的权限 # 验证用户是否有某个资产和系统用户的权限
path('asset-permission/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), path('asset-permission/user/validate/', api.ValidateUserAssetPermissionApi.as_view(),
name='validate-user-asset-permission'), name='validate-user-asset-permission'),
path('asset-permission/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(),
name='get-user-asset-permission-actions'),
] ]
urlpatterns += router.urls urlpatterns += router.urls

View File

@ -1,6 +1,5 @@
# coding: utf-8 # coding: utf-8
from __future__ import absolute_import, unicode_literals
import uuid import uuid
from collections import defaultdict from collections import defaultdict
import json import json
@ -13,7 +12,7 @@ from django.conf import settings
from common.utils import get_logger from common.utils import get_logger
from common.tree import TreeNode from common.tree import TreeNode
from .models import AssetPermission from .models import AssetPermission, Action
from .hands import Node from .hands import Node
logger = get_logger(__file__) logger = get_logger(__file__)
@ -101,7 +100,7 @@ class AssetPermissionUtil:
"UserGroup": get_user_group_permissions, "UserGroup": get_user_group_permissions,
"Asset": get_asset_permissions, "Asset": get_asset_permissions,
"Node": get_node_permissions, "Node": get_node_permissions,
"SystemUser": get_node_permissions, "SystemUser": get_system_user_permissions,
} }
CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_' CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_'
@ -180,6 +179,24 @@ class AssetPermissionUtil:
) )
return assets return assets
def _setattr_actions_to_system_user(self):
"""
动态给system_use设置属性actions
"""
for asset, system_users in self._assets.items():
# 获取资产和资产的祖先节点的所有授权规则
perms = get_asset_permissions(asset, include_node=True)
# 过滤当前self.permission的授权规则
perms = perms.filter(id__in=[perm.id for perm in self.permissions])
for system_user in system_users:
actions = set()
_perms = perms.filter(system_users=system_user).\
prefetch_related('actions')
for _perm in _perms:
actions.update(_perm.actions.all())
setattr(system_user, 'actions', actions)
def get_assets_without_cache(self): def get_assets_without_cache(self):
if self._assets: if self._assets:
return self._assets return self._assets
@ -192,6 +209,7 @@ class AssetPermissionUtil:
[s for s in system_users if s.protocol == asset.protocol] [s for s in system_users if s.protocol == asset.protocol]
) )
self._assets = assets self._assets = assets
self._setattr_actions_to_system_user()
return self._assets return self._assets
def get_cache_key(self, resource): def get_cache_key(self, resource):
@ -395,6 +413,7 @@ def parse_asset_to_tree_node(node, asset, system_users):
'protocol': system_user.protocol, 'protocol': system_user.protocol,
'priority': system_user.priority, 'priority': system_user.priority,
'login_mode': system_user.login_mode, 'login_mode': system_user.login_mode,
'actions': [action.name for action in system_user.actions],
'comment': system_user.comment, 'comment': system_user.comment,
}) })
data = { data = {
@ -423,3 +442,21 @@ def parse_asset_to_tree_node(node, asset, system_users):
} }
tree_node = TreeNode(**data) tree_node = TreeNode(**data)
return tree_node return tree_node
#
# actions
#
def check_system_user_action(system_user, action):
"""
:param system_user: SystemUser object (包含动态属性: actions)
:param action: Action object
:return: bool
"""
check_actions = [Action.get_action_all(), action]
granted_actions = getattr(system_user, 'actions', [])
actions = list(set(granted_actions).intersection(set(check_actions)))
return bool(actions)

View File

@ -11,8 +11,9 @@ from django.conf import settings
from common.permissions import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org from orgs.utils import current_org
from .hands import Node, Asset, SystemUser, User, UserGroup from .hands import Node, Asset, SystemUser, User, UserGroup
from .models import AssetPermission from .models import AssetPermission, Action
from .forms import AssetPermissionForm from .forms import AssetPermissionForm
from .const import PERMS_ACTION_NAME_ALL
class AssetPermissionListView(AdminUserRequiredMixin, TemplateView): class AssetPermissionListView(AdminUserRequiredMixin, TemplateView):
@ -46,6 +47,8 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
assets_id = assets_id.split(",") assets_id = assets_id.split(",")
assets = Asset.objects.filter(id__in=assets_id) assets = Asset.objects.filter(id__in=assets_id)
form['assets'].initial = assets form['assets'].initial = assets
form['actions'].initial = Action.objects.get(name=PERMS_ACTION_NAME_ALL)
return form return form
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):

View File

@ -538,7 +538,11 @@ jumpserver.initServerSideDataTable = function (options) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData)); $(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
} }
}, },
{className: 'text-center', targets: '_all'} {
targets: '_all',
className: 'text-center',
render: $.fn.dataTable.render.text()
}
]; ];
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
var select = { var select = {
@ -946,3 +950,10 @@ function rootNodeAddDom(ztree, callback) {
callback() callback()
}) })
} }
function htmlEscape ( d ) {
return typeof d === 'string' ?
d.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') :
d;
}

View File

@ -2,10 +2,8 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static "css/plugins/datatables/datatables.min.css" %}" rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script> <script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/datatables/datatables.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">

View File

@ -47,7 +47,8 @@
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }} help-message" > <div class="alert alert-{{ message.tags }} help-message" >
{{ message|safe }} {# {{ message|safe }}#}
{{ message }}
<button aria-hidden="true" data-dismiss="alert" class="close" type="button" style="outline: none;">×</button> <button aria-hidden="true" data-dismiss="alert" class="close" type="button" style="outline: none;">×</button>
</div> </div>

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import serializers from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import Terminal, Status, Session, Task from ..models import Terminal, Status, Session, Task
@ -29,7 +29,7 @@ class SessionSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta: class Meta:
model = Session model = Session
list_serializer_class = BulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = '__all__' fields = '__all__'
@ -44,7 +44,7 @@ class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta: class Meta:
fields = '__all__' fields = '__all__'
model = Task model = Task
list_serializer_class = BulkListSerializer list_serializer_class = AdaptedBulkListSerializer
class ReplaySerializer(serializers.Serializer): class ReplaySerializer(serializers.Serializer):

View File

@ -50,6 +50,7 @@ function initTable() {
buttons: [], buttons: [],
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "terminal:terminal-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "terminal:terminal-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},

View File

@ -5,6 +5,7 @@ from django.core.cache import cache
from django.contrib.auth import logout from django.contrib.auth import logout
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework import status
from rest_framework import generics from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
@ -52,9 +53,72 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet):
self.permission_classes = (IsOrgAdminOrAppUser,) self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions() return super().get_permissions()
def _deny_permission(self, instance):
"""
check current user has permission to handle instance
(update, destroy, bulk_update, bulk destroy)
"""
return not self.request.user.is_superuser and instance.is_superuser
def destroy(self, request, *args, **kwargs):
"""
rewrite because limit org_admin destroy superuser
"""
instance = self.get_object()
if self._deny_permission(instance):
data = {'msg': _("You do not have permission.")}
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
return super().destroy(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
"""
rewrite because limit org_admin update superuser
"""
instance = self.get_object()
if self._deny_permission(instance):
data = {'msg': _("You do not have permission.")}
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
return super().update(request, *args, **kwargs)
def _bulk_deny_permission(self, instances):
deny_instances = [i for i in instances if self._deny_permission(i)]
if len(deny_instances) > 0:
return True
else:
return False
def allow_bulk_destroy(self, qs, filtered): def allow_bulk_destroy(self, qs, filtered):
if self._bulk_deny_permission(filtered):
return False
return qs.count() != filtered.count() return qs.count() != filtered.count()
def bulk_update(self, request, *args, **kwargs):
"""
rewrite because limit org_admin update superuser
"""
partial = kwargs.pop('partial', False)
# restrict the update to the filtered queryset
queryset = self.filter_queryset(self.get_queryset())
if self._bulk_deny_permission(queryset):
data = {'msg': _("You do not have permission.")}
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
serializer = self.get_serializer(
queryset, data=request.data, many=True, partial=partial,
)
try:
serializer.is_valid(raise_exception=True)
except Exception as e:
data = {'error': str(e)}
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
self.perform_bulk_update(serializer)
return Response(serializer.data, status=status.HTTP_200_OK)
class UserChangePasswordApi(generics.RetrieveUpdateAPIView): class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)

View File

@ -3,24 +3,28 @@
# #
import uuid import uuid
import base64 import base64
import string
import random
from collections import OrderedDict from collections import OrderedDict
from django.conf import settings from django.conf import settings
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core import signing
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.shortcuts import reverse from django.shortcuts import reverse
from common.utils import get_signer, date_expired_default from common.utils import get_signer, date_expired_default, get_logger
__all__ = ['User'] __all__ = ['User']
signer = get_signer() signer = get_signer()
logger = get_logger(__file__)
class User(AbstractUser): class User(AbstractUser):
ROLE_ADMIN = 'Admin' ROLE_ADMIN = 'Admin'
@ -47,6 +51,9 @@ class User(AbstractUser):
(SOURCE_OPENID, 'OpenID'), (SOURCE_OPENID, 'OpenID'),
(SOURCE_RADIUS, 'Radius'), (SOURCE_RADIUS, 'Radius'),
) )
CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField( username = models.CharField(
max_length=128, unique=True, verbose_name=_('Username') max_length=128, unique=True, verbose_name=_('Username')
@ -346,9 +353,32 @@ class User(AbstractUser):
return user_default return user_default
def generate_reset_token(self): def generate_reset_token(self):
return signer.sign_t( letter = string.ascii_letters + string.digits
{'reset': str(self.id), 'email': self.email}, expires_in=3600 token =''.join([random.choice(letter) for _ in range(50)])
) self.set_cache(token)
return token
def set_cache(self, token):
key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
cache.set(key, {'id': self.id, 'email': self.email}, 3600)
@classmethod
def validate_reset_password_token(cls, token):
try:
key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
value = cache.get(key)
user_id = value.get('id', '')
email = value.get('email', '')
user = cls.objects.get(id=user_id, email=email)
except (AttributeError, cls.DoesNotExist) as e:
logger.error(e, exc_info=True)
user = None
return user
@classmethod
def expired_reset_password_token(cls, token):
key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
cache.delete(key)
@property @property
def otp_enabled(self): def otp_enabled(self):
@ -400,18 +430,6 @@ class User(AbstractUser):
access_key = app.create_access_key() access_key = app.create_access_key()
return app, access_key return app, access_key
@classmethod
def validate_reset_token(cls, token):
try:
data = signer.unsign_t(token)
user_id = data.get('reset', None)
user_email = data.get('email', '')
user = cls.objects.get(id=user_id, email=user_email)
except (signing.BadSignature, cls.DoesNotExist):
user = None
return user
def reset_password(self, new_password): def reset_password(self, new_password):
self.set_password(new_password) self.set_password(new_password)
self.date_password_last_updated = timezone.now() self.date_password_last_updated = timezone.now()

View File

@ -3,10 +3,10 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from rest_framework_bulk import BulkListSerializer
from common.utils import get_signer, validate_ssh_public_key from common.utils import get_signer, validate_ssh_public_key
from common.mixins import BulkSerializerMixin from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import User, UserGroup from ..models import User, UserGroup
signer = get_signer() signer = get_signer()
@ -16,7 +16,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
list_serializer_class = BulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = [ fields = [
'id', 'name', 'username', 'email', 'groups', 'groups_display', 'id', 'name', 'username', 'email', 'groups', 'groups_display',
'role', 'role_display', 'avatar_url', 'wechat', 'phone', 'role', 'role_display', 'avatar_url', 'wechat', 'phone',
@ -52,7 +52,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta: class Meta:
model = UserGroup model = UserGroup
list_serializer_class = BulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = '__all__' fields = '__all__'
read_only_fields = ['created_by'] read_only_fields = ['created_by']

View File

@ -22,11 +22,11 @@
<a href="{% url 'users:user-granted-asset' pk=user_object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a> <a href="{% url 'users:user-granted-asset' pk=user_object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
</li> </li>
<li class="pull-right"> <li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'users:user-update' pk=user_object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a> <a class="btn btn-outline {% if user_object.is_superuser and not request.user.is_superuser %} disabled {% else %} btn-default {% endif %}" href="{% url 'users:user-update' pk=user_object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li> </li>
<li class="pull-right"> <li class="pull-right">
<a class="btn btn-outline {% if request.user != user_object and user_object.username != "admin" %} btn-danger btn-delete-user {% else %} disabled {% endif %}"> <a class="btn btn-outline {% if request.user == user_object or user_object.username == "admin" or user_object.is_superuser and not request.user.is_superuser %} disabled {% else %} btn-danger btn-delete-user {% endif %}">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %} <i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a> </a>
</li> </li>

View File

@ -77,6 +77,7 @@ function initTable() {
ele: $('#user_assets_table'), ele: $('#user_assets_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %} {% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>'; var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
@ -91,7 +92,8 @@ function initTable() {
{targets: 4, createdCell: function (td, cellData) { {targets: 4, createdCell: function (td, cellData) {
var users = []; var users = [];
$.each(cellData, function (id, data) { $.each(cellData, function (id, data) {
users.push(data.name); var name = htmlEscape(data.name);
users.push(name);
}); });
$(td).html(users.join(', ')) $(td).html(users.join(', '))
}} }}

View File

@ -77,6 +77,7 @@ function initTable() {
ele: $('#user_assets_table'), ele: $('#user_assets_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %} {% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>'; var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
@ -91,7 +92,8 @@ function initTable() {
{targets: 4, createdCell: function (td, cellData) { {targets: 4, createdCell: function (td, cellData) {
var users = []; var users = [];
$.each(cellData, function (id, data) { $.each(cellData, function (id, data) {
users.push(data.name); var name = htmlEscape(data.name);
users.push(name);
}); });
$(td).html(users.join(', ')) $(td).html(users.join(', '))
}} }}

View File

@ -28,6 +28,7 @@ $(document).ready(function() {
buttons: [], buttons: [],
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "users:user-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "users:user-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},
@ -36,6 +37,7 @@ $(document).ready(function() {
$(td).html(html); $(td).html(html);
}}, }},
{targets: 3, createdCell: function (td, cellData) { {targets: 3, createdCell: function (td, cellData) {
cellData = htmlEscape(cellData);
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData; var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}}, }},

View File

@ -59,6 +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) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "users:user-detail" pk=DEFAULT_PK %}">' + 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));
}}, }},
@ -77,10 +78,16 @@ function initTable() {
} }
}}, }},
{targets: 7, createdCell: function (td, cellData, rowData) { {targets: 7, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData); var update_btn = "";
if (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin')) {
update_btn = '<a class="btn btn-xs disabled btn-info">{% trans "Update" %}</a>';
}
else{
update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
}
var del_btn = ""; var del_btn = "";
if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}") { if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}" || (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin'))) {
del_btn = '<a class="btn btn-xs btn-danger m-l-xs" disabled>{% trans "Delete" %}</a>' del_btn = '<a class="btn btn-xs btn-danger m-l-xs" disabled>{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData) .replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', rowData.name); .replace('99991938', rowData.name);

View File

@ -1,6 +1,7 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.cache import cache
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import RedirectView from django.views.generic import RedirectView
@ -84,7 +85,7 @@ class UserResetPasswordView(TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
token = request.GET.get('token', '') token = request.GET.get('token', '')
user = User.validate_reset_token(token) user = User.validate_reset_password_token(token)
if not user: if not user:
kwargs.update({'errors': _('Token invalid or expired')}) kwargs.update({'errors': _('Token invalid or expired')})
else: else:
@ -100,12 +101,12 @@ class UserResetPasswordView(TemplateView):
if password != password_confirm: if password != password_confirm:
return self.get(request, errors=_('Password not same')) return self.get(request, errors=_('Password not same'))
user = User.validate_reset_token(token) user = User.validate_reset_password_token(token)
if not user:
return self.get(request, errors=_('Token invalid or expired'))
if not user.can_update_password(): if not user.can_update_password():
error = _('User auth from {}, go there change password'.format(user.source)) error = _('User auth from {}, go there change password'.format(user.source))
return self.get(request, errors=error) return self.get(request, errors=error)
if not user:
return self.get(request, errors=_('Token invalid or expired'))
is_ok = check_password_rules(password) is_ok = check_password_rules(password)
if not is_ok: if not is_ok:
@ -115,6 +116,7 @@ class UserResetPasswordView(TemplateView):
) )
user.reset_password(password) user.reset_password(password)
User.expired_reset_password_token(token)
return HttpResponseRedirect(reverse('users:reset-password-success')) return HttpResponseRedirect(reverse('users:reset-password-success'))

View File

@ -107,6 +107,15 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
success_url = reverse_lazy('users:user-list') success_url = reverse_lazy('users:user-list')
success_message = update_success_msg success_message = update_success_msg
def _deny_permission(self):
obj = self.get_object()
return not self.request.user.is_superuser and obj.is_superuser
def get(self, request, *args, **kwargs):
if self._deny_permission():
return redirect(self.success_url)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
check_rules = get_password_check_rules() check_rules = get_password_check_rules()
context = { context = {

View File

@ -7,4 +7,4 @@ if [ ! -d $backup_dir ];then
mkdir $backup_dir mkdir $backup_dir
fi fi
mysqldump -uroot -h127.0.0.1 -p jumpserver > ${backup_dir}/jumpserver_$(date +'%Y-%m-%d_%H:%M:%S').sql mysqldump -uroot -h127.0.0.1 -p jumpserver -P3307 > ${backup_dir}/jumpserver_$(date +'%Y-%m-%d_%H:%M:%S').sql