* [Update] 拆分user permission

* [Update] 修改 ops command

* [Update] 修改setting无法生效的问题

* [Update] 修复授权详情-授权资产或节点添加资产失败

* [Bugfix] 修复组织管理员运行命令时的问题

* [Update] 修复命令执行左侧树点击问题
pull/3236/head
老广 2019-09-17 12:34:47 +08:00 committed by GitHub
parent 4931737164
commit 1457281b73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 767 additions and 813 deletions

View File

@ -92,7 +92,7 @@ function syncSelectedAssetsToModalTable(assetModalTable) {
}
// input assets有table assets没选则选中(click)
if (inputAssets !== null) {
if (inputAssets) {
assetModalTable.selected = inputAssets;
$.each(inputAssets, function (index, assetId) {
var dom = document.getElementById(assetId);

View File

@ -2,16 +2,6 @@ from __future__ import unicode_literals
import sys
from django.apps import AppConfig
from django.dispatch import receiver
from django.db.backends.signals import connection_created
@receiver(connection_created)
def on_db_connection_ready(sender, **kwargs):
from .signals import django_ready
if 'migrate' not in sys.argv:
django_ready.send(CommonConfig)
connection_created.disconnect(on_db_connection_ready)
class CommonConfig(AppConfig):
@ -19,3 +9,6 @@ class CommonConfig(AppConfig):
def ready(self):
from . import signals_handlers
from .signals import django_ready
if 'migrate' not in sys.argv:
django_ready.send(CommonConfig)

View File

@ -30,10 +30,10 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet):
util = AssetPermissionUtilV2(self.request.user)
util.filter_permissions(system_users=system_user.id)
permed_assets = util.get_assets().filter(id__in=[a.id for a in assets])
unpermed_assets = set(assets) - set(permed_assets)
if unpermed_assets:
invalid_assets = set(assets) - set(permed_assets)
if invalid_assets:
msg = _("Not has host {} permission").format(
[str(a.id) for a in unpermed_assets]
[str(a.id) for a in invalid_assets]
)
raise ValidationError({"hosts": msg})

View File

@ -4,13 +4,19 @@
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}"
rel="stylesheet">
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}"/>
<link href="{% static 'css/plugins/codemirror/codemirror.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/codemirror/ambiance.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.exhide.min.js' %}"></script>
<link href="{% static 'css/plugins/codemirror/codemirror.css' %}"
rel="stylesheet">
<link href="{% static 'css/plugins/codemirror/ambiance.css' %}"
rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}"
rel="stylesheet">
<script type="text/javascript"
src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script type="text/javascript"
src="{% static 'js/plugins/ztree/jquery.ztree.exhide.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
<script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script>
@ -24,6 +30,7 @@
top: 0;
padding: 10px;
}
.select2-container .select2-selection--single {
height: 34px;
}
@ -35,9 +42,12 @@
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="ibox-content mailbox-content"
style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
<div id="assetTree" class="ztree"></div>
<div id="assetTree" class="ztree">
{% trans 'Loading' %} ..
</div>
<div class="clearfix"></div>
</div>
</div>
@ -45,30 +55,39 @@
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
<div class="btn btn-sm btn-primary tree-toggle-btn"
onclick="toggle()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header" style="padding-top: 5px;">
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" onsubmit="return execute()">
<form enctype="multipart/form-data" method="post"
class="form-horizontal" action=""
onsubmit="return execute()">
<div class="form-group">
<div id="term" style="height: 100%;width: 100%"></div>
<div id="term"
style="height: 100%;width: 100%"></div>
</div>
<div class="row">
<div class="col-lg-10">
<div class="input-group" style="height: 100%; width: 100%">
<textarea class="form-control" id="command-text"></textarea>
<div class="input-group"
style="height: 100%; width: 100%">
<textarea class="form-control"
id="command-text"></textarea>
</div>
</div>
<div class="col-lg-2">
<select class="select2 form-control" id="system-users-select">
<select class="select2 form-control"
id="system-users-select">
{% for s in system_users %}
{% if s.protocol == 'ssh' and s.login_mode == 'auto' %}
<option value="{{ s.id }}">{{ s }}</option>
{% endif %}
{% endfor %}
</select>
<button type="button" class="btn btn-primary btn-execute" style="margin-top: 30px; width: 100%">{% trans 'Go' %}</button>
<button type="button"
class="btn btn-primary btn-execute"
style="margin-top: 30px; width: 100%">{% trans 'Go' %}</button>
</div>
</div>
</form>
@ -83,7 +102,7 @@
var zTree, show = 0;
var systemUserId = null;
var url = null;
var treeUrl = "{% url 'api-perms:my-nodes-children-with-assets-as-tree' %}?cache_policy=1";
var treeUrl = "{% url 'api-perms:my-nodes-with-assets-as-tree' %}?cache_policy=1";
function proposeGeometry(term) {
if (!term.element.parentElement) {
@ -123,8 +142,7 @@ function fit(term) {
function initTree() {
if (systemUserId) {
url = treeUrl + '&system_user=' + systemUserId
}
else{
} else {
url = treeUrl
}
var setting = {

View File

@ -9,6 +9,7 @@ from common.permissions import (
PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor
)
from common.mixins import DatetimeSearchMixin
from orgs.utils import tmp_to_root_org
from ..models import CommandExecution
from ..forms import CommandExecutionForm
@ -67,6 +68,7 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
def get_user_system_users(self):
from perms.utils import AssetPermissionUtilV2
user = self.request.user
with tmp_to_root_org():
util = AssetPermissionUtilV2(user)
system_users = [s for s in util.get_system_users() if s.protocol == 'ssh']
return system_users

View File

@ -1,29 +1,20 @@
# -*- coding: utf-8 -*-
#
import uuid
from hashlib import md5
from django.core.cache import cache
from django.db.models import Q
from django.conf import settings
from rest_framework.views import Response
from django.utils.decorators import method_decorator
from django.views.decorators.http import condition
from rest_framework.generics import get_object_or_404
from django.utils.translation import ugettext as _
from assets.utils import LabelFilterMixin
from common.permissions import IsValidUser, IsOrgAdminOrAppUser, IsOrgAdmin
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.utils import get_logger
from orgs.utils import set_to_root_org
from ..hands import User, Asset, Node, SystemUser
from ..hands import User, Asset, SystemUser
from .. import serializers
from .. import const
logger = get_logger(__name__)
__all__ = [
'UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin',
'UserPermissionMixin',
]
@ -54,147 +45,6 @@ class UserPermissionMixin:
return super().get_permissions()
# def get_etag(request, *args, **kwargs):
# cache_policy = request.GET.get("cache_policy")
# if cache_policy != '1':
# return None
# if not UserPermissionCacheMixin.CACHE_ENABLE:
# return None
# view = request.parser_context.get("view")
# if not view:
# return None
# etag = view.get_meta_cache_id()
# return etag
class UserPermissionCacheMixin:
pass
# cache_policy = '0'
# RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}'
# CACHE_ENABLE = settings.ASSETS_PERM_CACHE_ENABLE
# CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
# _object = None
#
# def get_object(self):
# return None
#
# # 内部使用可控制缓存
# def _get_object(self):
# if not self._object:
# self._object = self.get_object()
# return self._object
#
# def get_object_id(self):
# obj = self._get_object()
# if obj:
# return str(obj.id)
# return None
#
# def get_request_md5(self):
# path = self.request.path
# query = {k: v for k, v in self.request.GET.items()}
# query.pop("_", None)
# query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
# full_path = "{}?{}".format(path, query)
# return md5(full_path.encode()).hexdigest()
#
# def get_meta_cache_id(self):
# obj = self._get_object()
# util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
# meta_cache_id = util.cache_meta.get('id')
# return meta_cache_id
#
# def get_response_cache_id(self):
# obj_id = self.get_object_id()
# request_md5 = self.get_request_md5()
# meta_cache_id = self.get_meta_cache_id()
# resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
# return resp_cache_id
#
# def get_response_from_cache(self):
# # 没有数据缓冲
# meta_cache_id = self.get_meta_cache_id()
# if not meta_cache_id:
# logger.debug("Not get meta id: {}".format(meta_cache_id))
# return None
# # 从响应缓冲里获取响应
# key = self.get_response_key()
# data = cache.get(key)
# if not data:
# logger.debug("Not get response from cache: {}".format(key))
# return None
# logger.debug("Get user permission from cache: {}".format(self.get_object()))
# response = Response(data)
# return response
#
# def expire_response_cache(self):
# obj_id = self.get_object_id()
# expire_cache_id = '{}_{}'.format(obj_id, '*')
# key = self.RESP_CACHE_KEY.format(expire_cache_id)
# cache.delete_pattern(key)
#
# def get_response_key(self):
# resp_cache_id = self.get_response_cache_id()
# key = self.RESP_CACHE_KEY.format(resp_cache_id)
# return key
#
# def set_response_to_cache(self, response):
# key = self.get_response_key()
# cache.set(key, response.data, self.CACHE_TIME)
# logger.debug("Set response to cache: {}".format(key))
#
# @method_decorator(condition(etag_func=get_etag))
# def get(self, request, *args, **kwargs):
# if not self.CACHE_ENABLE:
# self.cache_policy = '0'
# else:
# self.cache_policy = request.GET.get('cache_policy', '0')
#
# obj = self._get_object()
# if obj is None:
# logger.debug("Not get response from cache: obj is none")
# return super().get(request, *args, **kwargs)
#
# if AssetPermissionUtil.is_not_using_cache(self.cache_policy):
# logger.debug("Not get resp from cache: {}".format(self.cache_policy))
# return super().get(request, *args, **kwargs)
# elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
# logger.debug("Not get resp from cache: {}".format(self.cache_policy))
# self.expire_response_cache()
#
# logger.debug("Try get response from cache")
# resp = self.get_response_from_cache()
# if not resp:
# resp = super().get(request, *args, **kwargs)
# self.set_response_to_cache(resp)
# return resp
class NodesWithUngroupMixin:
util = None
@staticmethod
def get_ungrouped_node(ungroup_key):
return Node(key=ungroup_key, id=const.UNGROUPED_NODE_ID,
value=_("ungrouped"))
@staticmethod
def get_empty_node():
return Node(key=const.EMPTY_NODE_KEY, id=const.EMPTY_NODE_ID,
value=_("empty"))
def add_ungrouped_nodes(self, node_map, node_keys):
ungroup_key = '1:-1'
for key in node_keys:
if key.endswith('-1'):
ungroup_key = key
break
ungroup_node = self.get_ungrouped_node(ungroup_key)
empty_node = self.get_empty_node()
node_map[ungroup_key] = ungroup_node
node_map[const.EMPTY_NODE_KEY] = empty_node
class GrantAssetsMixin(LabelFilterMixin):
serializer_class = serializers.AssetGrantedSerializer

View File

@ -1,321 +0,0 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response
from rest_framework.generics import (
ListAPIView, get_object_or_404, RetrieveAPIView
)
from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin
from common.tree import TreeNodeSerializer
from common.utils import get_logger
from ..utils import (
ParserNode, AssetPermissionUtilV2
)
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
from .. import serializers
from ..models import Action
from .mixin import UserPermissionMixin
logger = get_logger(__name__)
__all__ = [
'UserGrantedAssetsApi',
'UserGrantedAssetsAsTreeApi',
'UserGrantedNodeAssetsApi',
'UserGrantedNodesApi',
'UserGrantedNodesAsTreeApi',
'UserGrantedNodesWithAssetsAsTreeApi',
'UserGrantedNodeChildrenApi',
'UserGrantedNodeChildrenAsTreeApi',
'UserGrantedNodeChildrenWithAssetsAsTreeApi',
'RefreshAssetPermissionCacheApi',
'UserGrantedAssetSystemUsersApi',
'ValidateUserAssetPermissionApi',
'GetUserAssetPermissionActionsApi',
]
class UserAssetPermissionMixin(UserPermissionMixin):
util = None
def initial(self, *args, **kwargs):
super().initial(*args, *kwargs)
cache_policy = self.request.query_params.get('cache_policy', '0')
self.util = AssetPermissionUtilV2(self.obj, cache_policy=cache_policy)
class UserNodeTreeMixin:
serializer_class = TreeNodeSerializer
nodes_only_fields = ParserNode.nodes_only_fields
tree = None
def parse_nodes_to_queryset(self, nodes):
nodes = nodes.only(*self.nodes_only_fields)
_queryset = []
tree = self.util.get_user_tree()
for node in nodes:
assets_amount = tree.assets_amount(node.key)
if assets_amount == 0:
continue
node._assets_amount = assets_amount
data = ParserNode.parse_node_to_tree_node(node)
_queryset.append(data)
return _queryset
def get_serializer_queryset(self, queryset):
queryset = self.parse_nodes_to_queryset(queryset)
return queryset
def get_serializer(self, queryset, many=True, **kwargs):
queryset = self.get_serializer_queryset(queryset)
queryset.sort()
return super().get_serializer(queryset, many=many, **kwargs)
class UserAssetTreeMixin:
serializer_class = TreeNodeSerializer
nodes_only_fields = ParserNode.assets_only_fields
@staticmethod
def parse_assets_to_queryset(assets, node):
_queryset = []
for asset in assets:
data = ParserNode.parse_asset_to_tree_node(node, asset)
_queryset.append(data)
return _queryset
def get_serializer_queryset(self, queryset):
queryset = queryset.only(*self.nodes_only_fields)
_queryset = self.parse_assets_to_queryset(queryset, None)
return _queryset
def get_serializer(self, queryset, many=True, **kwargs):
queryset = self.get_serializer_queryset(queryset)
queryset.sort()
return super().get_serializer(queryset, many=many, **kwargs)
class UserGrantedAssetsApi(UserAssetPermissionMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
filter_fields = ['hostname', 'ip', 'id', 'comment']
search_fields = ['hostname', 'ip', 'comment']
def filter_by_nodes(self, queryset):
node_id = self.request.query_params.get("node")
if not node_id:
return queryset
node = get_object_or_404(Node, pk=node_id)
query_all = self.request.query_params.get("all", "0") in ["1", "true"]
if query_all:
pattern = '^{0}$|^{0}:'.format(node.key)
queryset = queryset.filter(nodes__key__regex=pattern).distinct()
else:
queryset = queryset.filter(nodes=node)
return queryset
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.filter_by_nodes(queryset)
return queryset
def get_queryset(self):
queryset = self.util.get_assets().only(*self.only_fields)
return queryset
class UserGrantedAssetsAsTreeApi(UserAssetTreeMixin, UserGrantedAssetsApi):
pass
class UserGrantedNodeAssetsApi(UserGrantedAssetsApi):
def get_queryset(self):
node_id = self.kwargs.get("node_id")
node = get_object_or_404(Node, pk=node_id)
deep = self.request.query_params.get("all", "0") == "1"
queryset = self.util.get_nodes_assets(node, deep=deep)\
.only(*self.only_fields)
return queryset
class UserGrantedNodesApi(UserAssetPermissionMixin, ListAPIView):
"""
查询用户授权的所有节点的API
"""
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.NodeGrantedSerializer
nodes_only_fields = NodeSerializer.Meta.only_fields
def get_serializer_context(self):
context = super().get_serializer_context()
context["tree"] = self.util.user_tree
return context
def get_queryset(self):
node_keys = self.util.get_nodes()
queryset = Node.objects.filter(key__in=node_keys)\
.only(*self.nodes_only_fields)
return queryset
class UserGrantedNodesAsTreeApi(UserNodeTreeMixin, UserGrantedNodesApi):
pass
class UserGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesAsTreeApi):
def get_serializer_queryset(self, queryset):
_queryset = super().get_serializer_queryset(queryset)
for node in queryset:
assets = self.util.get_nodes_assets(node)
_queryset.extend(
UserAssetTreeMixin.parse_assets_to_queryset(assets, node)
)
return _queryset
class UserGrantedNodeChildrenApi(UserGrantedNodesApi):
node = None
tree = None
root_keys = None # 如果是第一次访问,则需要把二级节点添加进去,这个 roots_keys
def get(self, request, *args, **kwargs):
key = self.request.query_params.get("key")
pk = self.request.query_params.get("id")
system_user_id = self.request.query_params.get("system_user")
if system_user_id:
self.util.filter_permissions(system_users=system_user_id)
self.tree = self.util.get_user_tree()
node = None
if pk is not None:
node = get_object_or_404(Node, id=pk)
elif key is not None:
node = get_object_or_404(Node, key=key)
self.node = node
return super().get(request, *args, **kwargs)
def get_queryset(self):
if self.node:
children = self.tree.children(self.node.key)
else:
children = self.tree.children(self.tree.root)
# 默认打开组织节点下的的节点
self.root_keys = [child.identifier for child in children]
for key in self.root_keys:
children.extend(self.tree.children(key))
node_keys = [n.identifier for n in children]
queryset = Node.objects.filter(key__in=node_keys)
return queryset
class UserGrantedNodeChildrenAsTreeApi(UserNodeTreeMixin, UserGrantedNodeChildrenApi):
pass
class UserGrantedNodeChildrenWithAssetsAsTreeApi(UserGrantedNodeChildrenAsTreeApi):
nodes_only_fields = ParserNode.nodes_only_fields
assets_only_fields = ParserNode.assets_only_fields
def get_serializer_queryset(self, queryset):
_queryset = super().get_serializer_queryset(queryset)
nodes = []
if self.node:
nodes.append(self.node)
elif self.root_keys:
nodes = Node.objects.filter(key__in=self.root_keys)
for node in nodes:
assets = self.util.get_nodes_assets(node).only(
*self.assets_only_fields
)
_queryset.extend(
UserAssetTreeMixin.parse_assets_to_queryset(assets, node)
)
return _queryset
class GetUserAssetPermissionActionsApi(UserAssetPermissionMixin, RetrieveAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.ActionsSerializer
def get_obj(self):
user_id = self.request.query_params.get('user_id', '')
user = get_object_or_404(User, id=user_id)
return user
def get_object(self):
asset_id = self.request.query_params.get('asset_id', '')
system_id = self.request.query_params.get('system_user_id', '')
try:
asset_id = uuid.UUID(asset_id)
system_id = uuid.UUID(system_id)
except ValueError:
return Response({'msg': False}, status=403)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_id)
system_users_actions = self.util.get_asset_system_users_with_actions(asset)
actions = system_users_actions.get(system_user)
return {"actions": actions}
class ValidateUserAssetPermissionApi(UserAssetPermissionMixin, APIView):
permission_classes = (IsOrgAdminOrAppUser,)
def get_obj(self):
user_id = self.request.query_params.get('user_id', '')
user = get_object_or_404(User, id=user_id)
return user
def get(self, request, *args, **kwargs):
asset_id = request.query_params.get('asset_id', '')
system_id = request.query_params.get('system_user_id', '')
action_name = request.query_params.get('action_name', '')
try:
asset_id = uuid.UUID(asset_id)
system_id = uuid.UUID(system_id)
except ValueError:
return Response({'msg': False}, status=403)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_id)
system_users_actions = self.util.get_asset_system_users_with_actions(asset)
actions = system_users_actions.get(system_user)
if action_name in Action.value_to_choices(actions):
return Response({'msg': True}, status=200)
return Response({'msg': False}, status=403)
class RefreshAssetPermissionCacheApi(RetrieveAPIView):
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
AssetPermissionUtilV2.expire_all_user_tree_cache()
return Response({'msg': True}, status=200)
class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetSystemUserSerializer
only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields
def get_queryset(self):
asset_id = self.kwargs.get('asset_id')
asset = get_object_or_404(Asset, id=asset_id)
system_users_with_actions = self.util.get_asset_system_users_with_actions(asset)
system_users = []
for system_user, actions in system_users_with_actions.items():
system_user.actions = actions
system_users.append(system_user)
system_users.sort(key=lambda x: x.priority)
return system_users

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
#
from .common import *
from .user_permission_nodes import *
from .user_permission_assets import *
from .user_permission_nodes_with_assets import *

View File

@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response
from rest_framework.generics import (
ListAPIView, get_object_or_404, RetrieveAPIView
)
from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin
from common.utils import get_logger
from ...utils import (
AssetPermissionUtilV2
)
from ...hands import User, Asset, SystemUser
from ... import serializers
from ...models import Action
from .mixin import UserAssetPermissionMixin
logger = get_logger(__name__)
__all__ = [
'RefreshAssetPermissionCacheApi',
'UserGrantedAssetSystemUsersApi',
'ValidateUserAssetPermissionApi',
'GetUserAssetPermissionActionsApi',
]
class GetUserAssetPermissionActionsApi(UserAssetPermissionMixin,
RetrieveAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.ActionsSerializer
def get_obj(self):
user_id = self.request.query_params.get('user_id', '')
user = get_object_or_404(User, id=user_id)
return user
def get_object(self):
asset_id = self.request.query_params.get('asset_id', '')
system_id = self.request.query_params.get('system_user_id', '')
try:
asset_id = uuid.UUID(asset_id)
system_id = uuid.UUID(system_id)
except ValueError:
return Response({'msg': False}, status=403)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_id)
system_users_actions = self.util.get_asset_system_users_with_actions(
asset)
actions = system_users_actions.get(system_user)
return {"actions": actions}
class ValidateUserAssetPermissionApi(UserAssetPermissionMixin, APIView):
permission_classes = (IsOrgAdminOrAppUser,)
def get_obj(self):
user_id = self.request.query_params.get('user_id', '')
user = get_object_or_404(User, id=user_id)
return user
def get(self, request, *args, **kwargs):
asset_id = request.query_params.get('asset_id', '')
system_id = request.query_params.get('system_user_id', '')
action_name = request.query_params.get('action_name', '')
try:
asset_id = uuid.UUID(asset_id)
system_id = uuid.UUID(system_id)
except ValueError:
return Response({'msg': False}, status=403)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_id)
system_users_actions = self.util.get_asset_system_users_with_actions(
asset)
actions = system_users_actions.get(system_user)
if action_name in Action.value_to_choices(actions):
return Response({'msg': True}, status=200)
return Response({'msg': False}, status=403)
class RefreshAssetPermissionCacheApi(RetrieveAPIView):
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
AssetPermissionUtilV2.expire_all_user_tree_cache()
return Response({'msg': True}, status=200)
class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetSystemUserSerializer
only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields
def get_queryset(self):
asset_id = self.kwargs.get('asset_id')
asset = get_object_or_404(Asset, id=asset_id)
system_users_with_actions = self.util.get_asset_system_users_with_actions(
asset)
system_users = []
for system_user, actions in system_users_with_actions.items():
system_user.actions = actions
system_users.append(system_user)
system_users.sort(key=lambda x: x.priority)
return system_users

View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
#
from ..mixin import UserPermissionMixin
from ...utils import AssetPermissionUtilV2, ParserNode
from ...hands import Node
from common.tree import TreeNodeSerializer
class UserAssetPermissionMixin(UserPermissionMixin):
util = None
tree = None
def initial(self, *args, **kwargs):
super().initial(*args, *kwargs)
cache_policy = self.request.query_params.get('cache_policy', '0')
system_user_id = self.request.query_params.get("system_user")
self.util = AssetPermissionUtilV2(self.obj, cache_policy=cache_policy)
if system_user_id:
self.util.filter_permissions(system_users=system_user_id)
self.tree = self.util.get_user_tree()
class UserNodeTreeMixin:
serializer_class = TreeNodeSerializer
nodes_only_fields = ParserNode.nodes_only_fields
def parse_nodes_to_queryset(self, nodes):
nodes = nodes.only(*self.nodes_only_fields)
_queryset = []
for node in nodes:
assets_amount = self.tree.assets_amount(node.key)
if assets_amount == 0 and node.key != Node.empty_key:
continue
node._assets_amount = assets_amount
data = ParserNode.parse_node_to_tree_node(node)
_queryset.append(data)
return _queryset
def get_serializer_queryset(self, queryset):
queryset = self.parse_nodes_to_queryset(queryset)
return queryset
def get_serializer(self, queryset, many=True, **kwargs):
queryset = self.get_serializer_queryset(queryset)
queryset.sort()
return super().get_serializer(queryset, many=many, **kwargs)
class UserAssetTreeMixin:
serializer_class = TreeNodeSerializer
nodes_only_fields = ParserNode.assets_only_fields
@staticmethod
def parse_assets_to_queryset(assets, node):
_queryset = []
for asset in assets:
data = ParserNode.parse_asset_to_tree_node(node, asset)
_queryset.append(data)
return _queryset
def get_serializer_queryset(self, queryset):
queryset = queryset.only(*self.nodes_only_fields)
_queryset = self.parse_assets_to_queryset(queryset, None)
return _queryset
def get_serializer(self, queryset, many=True, **kwargs):
queryset = self.get_serializer_queryset(queryset)
queryset.sort()
return super().get_serializer(queryset, many=many, **kwargs)

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
#
from django.shortcuts import get_object_or_404
from rest_framework.generics import (
ListAPIView, get_object_or_404
)
from common.permissions import IsOrgAdminOrAppUser
from common.utils import get_logger
from ...hands import Node
from ... import serializers
from .mixin import UserAssetPermissionMixin, UserAssetTreeMixin
logger = get_logger(__name__)
__all__ = [
'UserGrantedAssetsApi',
'UserGrantedAssetsAsTreeApi',
'UserGrantedNodeAssetsApi',
]
class UserGrantedAssetsApi(UserAssetPermissionMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
filter_fields = ['hostname', 'ip', 'id', 'comment']
search_fields = ['hostname', 'ip', 'comment']
def filter_by_nodes(self, queryset):
node_id = self.request.query_params.get("node")
if not node_id:
return queryset
node = get_object_or_404(Node, pk=node_id)
query_all = self.request.query_params.get("all", "0") in ["1", "true"]
if query_all:
pattern = '^{0}$|^{0}:'.format(node.key)
queryset = queryset.filter(nodes__key__regex=pattern).distinct()
else:
queryset = queryset.filter(nodes=node)
return queryset
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.filter_by_nodes(queryset)
return queryset
def get_queryset(self):
queryset = self.util.get_assets().only(*self.only_fields)
return queryset
class UserGrantedAssetsAsTreeApi(UserAssetTreeMixin, UserGrantedAssetsApi):
pass
class UserGrantedNodeAssetsApi(UserGrantedAssetsApi):
def get_queryset(self):
node_id = self.kwargs.get("node_id")
node = get_object_or_404(Node, pk=node_id)
deep = self.request.query_params.get("all", "0") == "1"
queryset = self.util.get_nodes_assets(node, deep=deep)\
.only(*self.only_fields)
return queryset

View File

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
#
from django.shortcuts import get_object_or_404
from rest_framework.generics import (
ListAPIView, get_object_or_404
)
from common.permissions import IsOrgAdminOrAppUser
from common.utils import get_logger
from ...hands import Node, NodeSerializer
from ... import serializers
from .mixin import UserNodeTreeMixin, UserAssetPermissionMixin
logger = get_logger(__name__)
__all__ = [
'UserGrantedNodesApi',
'UserGrantedNodesAsTreeApi',
'UserGrantedNodeChildrenApi',
'UserGrantedNodeChildrenAsTreeApi',
]
class UserGrantedNodesApi(UserAssetPermissionMixin, ListAPIView):
"""
查询用户授权的所有节点的API
"""
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.NodeGrantedSerializer
nodes_only_fields = NodeSerializer.Meta.only_fields
def get_serializer_context(self):
context = super().get_serializer_context()
if self.serializer_class == serializers.NodeGrantedSerializer:
context["tree"] = self.tree
return context
def get_queryset(self):
node_keys = self.util.get_nodes()
queryset = Node.objects.filter(key__in=node_keys)\
.only(*self.nodes_only_fields)
return queryset
class UserGrantedNodesAsTreeApi(UserNodeTreeMixin, UserGrantedNodesApi):
pass
class UserGrantedNodeChildrenApi(UserGrantedNodesApi):
node = None
root_keys = None # 如果是第一次访问,则需要把二级节点添加进去,这个 roots_keys
def get(self, request, *args, **kwargs):
key = self.request.query_params.get("key")
pk = self.request.query_params.get("id")
node = None
if pk is not None:
node = get_object_or_404(Node, id=pk)
elif key is not None:
node = get_object_or_404(Node, key=key)
self.node = node
return super().get(request, *args, **kwargs)
def get_queryset(self):
if self.node:
children = self.tree.children(self.node.key)
else:
children = self.tree.children(self.tree.root)
# 默认打开组织节点下的节点
self.root_keys = [child.identifier for child in children]
for key in self.root_keys:
children.extend(self.tree.children(key))
node_keys = [n.identifier for n in children]
queryset = Node.objects.filter(key__in=node_keys)
return queryset
class UserGrantedNodeChildrenAsTreeApi(UserNodeTreeMixin, UserGrantedNodeChildrenApi):
pass

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
#
from common.utils import get_logger
from ...utils import ParserNode
from .mixin import UserAssetTreeMixin
from ...hands import Node
from .user_permission_nodes import UserGrantedNodesAsTreeApi
from .user_permission_nodes import UserGrantedNodeChildrenAsTreeApi
logger = get_logger(__name__)
__all__ = [
'UserGrantedNodesAsTreeApi',
'UserGrantedNodesWithAssetsAsTreeApi',
'UserGrantedNodeChildrenAsTreeApi',
'UserGrantedNodeChildrenWithAssetsAsTreeApi',
]
class UserGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesAsTreeApi):
assets_only_fields = ParserNode.assets_only_fields
def get_serializer_queryset(self, queryset):
_queryset = super().get_serializer_queryset(queryset)
_all_assets = self.util.get_assets().only(*self.assets_only_fields)
_all_assets_map = {a.id: a for a in _all_assets}
for node in queryset:
assets_ids = self.tree.assets(node.key)
assets = [_all_assets_map[_id] for _id in assets_ids if _id in _all_assets_map]
_queryset.extend(
UserAssetTreeMixin.parse_assets_to_queryset(assets, node)
)
return _queryset
class UserGrantedNodeChildrenWithAssetsAsTreeApi(UserGrantedNodeChildrenAsTreeApi):
nodes_only_fields = ParserNode.nodes_only_fields
assets_only_fields = ParserNode.assets_only_fields
def get_serializer_queryset(self, queryset):
_queryset = super().get_serializer_queryset(queryset)
nodes = []
if self.node:
nodes.append(self.node)
elif self.root_keys:
nodes = Node.objects.filter(key__in=self.root_keys)
for node in nodes:
assets = self.util.get_nodes_assets(node).only(
*self.assets_only_fields
)
_queryset.extend(
UserAssetTreeMixin.parse_assets_to_queryset(assets, node)
)
return _queryset

View File

@ -75,7 +75,7 @@
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select assets' %}" class="select2" id="asset_select2" style="width: 100%" multiple="" tabindex="4">
<select data-placeholder="{% trans 'Select assets' %}" class="select2" id="id_assets" style="width: 100%" multiple="" tabindex="4">
</select>
</td>
</tr>
@ -207,7 +207,7 @@ $(document).ready(function () {
var nodeListUrl = "{% url 'api-assets:node-list' %}";
nodesSelect2Init(".nodes-select2", nodeListUrl);
$("#asset_select2").parent().find(".select2-selection").on('click', function (e) {
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
@ -216,7 +216,7 @@ $(document).ready(function () {
})
})
.on('click', '.btn-add-assets', function () {
var assets_selected = $("#asset_select2 option:selected").map(function () {
var assets_selected = $("#id_assets option:selected").map(function () {
return $(this).attr('value');
}).get();
if (assets_selected.length === 0) {

View File

@ -4,6 +4,8 @@ import pickle
import threading
from collections import defaultdict
from functools import reduce
from hashlib import md5
import json
from django.core.cache import cache
from django.db.models import Q
@ -60,29 +62,33 @@ def get_system_user_permissions(system_user):
class AssetPermissionUtilCacheMixin:
user_tree_cache_key = 'USER_PERM_TREE_{}'
user_tree_cache_key = 'USER_PERM_TREE_{}_{}'
user_tree_cache_ttl = settings.ASSETS_PERM_CACHE_TIME
user_tree_cache_enable = settings.ASSETS_PERM_CACHE_ENABLE
cache_policy = '0'
obj_id = ''
_filter_id = 'None'
@property
def cache_key(self):
return self.user_tree_cache_key.format(self.obj_id, self._filter_id)
def expire_user_tree_cache(self):
key = self.user_tree_cache_key.format(self.obj_id)
cache.delete(key)
cache.delete(self.cache_key)
@classmethod
def expire_all_user_tree_cache(cls):
key = cls.user_tree_cache_key.format('*')
key = cls.user_tree_cache_key.format('*', '*')
key = key.split('_')[:-1]
key = '_'.join(key)
cache.delete_pattern(key)
def set_user_tree_to_cache(self, user_tree):
data = pickle.dumps(user_tree)
key = self.user_tree_cache_key.format(self.obj_id)
cache.set(key, data, self.user_tree_cache_ttl)
cache.set(self.cache_key, data, self.user_tree_cache_ttl)
def get_user_tree_from_cache(self):
key = self.user_tree_cache_key.format(self.obj_id)
data = cache.get(key)
data = cache.get(self.cache_key)
if not data:
return None
user_tree = pickle.loads(data)
@ -129,6 +135,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self._filter_id = 'None' # 当通过filter更改 permission是标记
self.change_org_if_need()
self._user_tree = None
self._user_tree_filter_id = 'None'
self.full_tree = Node.tree()
self.mutex = threading.Lock()
@ -148,7 +155,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
@timeit
def filter_permissions(self, **filters):
filters_json = json.dumps(filters, sort_keys=True)
self._permissions = self.permissions.filter(**filters)
self._filter_id = md5(filters_json.encode()).hexdigest()
@property
def user_tree(self):
@ -282,15 +291,25 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
parent=user_tree.root,
)
def set_user_tree_to_local(self, user_tree):
self._user_tree = user_tree
self._user_tree_filter_id = self._filter_id
def get_user_tree_from_local(self):
if self._user_tree and self._user_tree_filter_id == self._filter_id:
return self._user_tree
return None
@timeit
def get_user_tree(self):
# 使用锁保证多次获取tree的时候顺序执行可以使用缓存
with self.mutex:
if self._user_tree:
return self._user_tree
user_tree = self.get_user_tree_from_local()
if user_tree:
return user_tree
user_tree = self.get_user_tree_from_cache_if_need()
if user_tree:
self._user_tree = user_tree
self.set_user_tree_to_local(user_tree)
return user_tree
user_tree = TreeService()
full_tree_root = self.full_tree.root_node()
@ -303,7 +322,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(user_tree)
self.set_user_tree_to_cache_if_need(user_tree)
self._user_tree = user_tree
self.set_user_tree_to_local(user_tree)
return user_tree
# Todo: 是否可以获取多个资产的系统用户

View File

@ -22,7 +22,7 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
instance.refresh_setting()
@receiver(django_ready, dispatch_uid="my_unique_identifier")
@receiver(django_ready)
def monkey_patch_settings(sender, **kwargs):
logger.debug("Monkey patch settings")
cache_key_prefix = '_SETTING_'