mirror of https://github.com/jumpserver/jumpserver
commit
a025930957
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_{}'
|
||||||
|
|
||||||
|
|
|
@ -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__'
|
||||||
|
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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));
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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));
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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));
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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));
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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));
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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));
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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/',
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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_"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from .mixins import BulkListSerializerMixin
|
||||||
|
from rest_framework_bulk.serializers import BulkListSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class AdaptedBulkListSerializer(BulkListSerializerMixin, BulkListSerializer):
|
||||||
|
pass
|
|
@ -1,3 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
VERSION = '1.4.9'
|
VERSION = '1.4.10'
|
||||||
|
|
|
@ -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
|
@ -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);
|
||||||
|
|
|
@ -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__'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')),
|
||||||
|
)
|
|
@ -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')
|
||||||
|
|
|
@ -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)
|
||||||
|
]
|
|
@ -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)
|
||||||
|
]
|
|
@ -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'))
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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));
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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, '<').replace(/>/g, '>').replace(/"/g, '"') :
|
||||||
|
d;
|
||||||
|
}
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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));
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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,)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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']
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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(', '))
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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(', '))
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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>');
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue