Unverified Commit 1457281b authored by 老广's avatar 老广 Committed by GitHub

Bugfix (#3232)

* [Update] 拆分user permission

* [Update] 修改 ops command

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

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

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

* [Update] 修复命令执行左侧树点击问题
parent 49317371
...@@ -92,7 +92,7 @@ function syncSelectedAssetsToModalTable(assetModalTable) { ...@@ -92,7 +92,7 @@ function syncSelectedAssetsToModalTable(assetModalTable) {
} }
// input assets有,table assets没选,则选中(click) // input assets有,table assets没选,则选中(click)
if (inputAssets !== null) { if (inputAssets) {
assetModalTable.selected = inputAssets; assetModalTable.selected = inputAssets;
$.each(inputAssets, function (index, assetId) { $.each(inputAssets, function (index, assetId) {
var dom = document.getElementById(assetId); var dom = document.getElementById(assetId);
......
...@@ -2,16 +2,6 @@ from __future__ import unicode_literals ...@@ -2,16 +2,6 @@ from __future__ import unicode_literals
import sys import sys
from django.apps import AppConfig 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): class CommonConfig(AppConfig):
...@@ -19,3 +9,6 @@ class CommonConfig(AppConfig): ...@@ -19,3 +9,6 @@ class CommonConfig(AppConfig):
def ready(self): def ready(self):
from . import signals_handlers from . import signals_handlers
from .signals import django_ready
if 'migrate' not in sys.argv:
django_ready.send(CommonConfig)
...@@ -30,10 +30,10 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet): ...@@ -30,10 +30,10 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet):
util = AssetPermissionUtilV2(self.request.user) util = AssetPermissionUtilV2(self.request.user)
util.filter_permissions(system_users=system_user.id) util.filter_permissions(system_users=system_user.id)
permed_assets = util.get_assets().filter(id__in=[a.id for a in assets]) permed_assets = util.get_assets().filter(id__in=[a.id for a in assets])
unpermed_assets = set(assets) - set(permed_assets) invalid_assets = set(assets) - set(permed_assets)
if unpermed_assets: if invalid_assets:
msg = _("Not has host {} permission").format( 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}) raise ValidationError({"hosts": msg})
......
...@@ -4,347 +4,365 @@ ...@@ -4,347 +4,365 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% block custom_head_css_js %} {% 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' %}"
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" /> rel="stylesheet">
<link href="{% static 'css/plugins/codemirror/codemirror.css' %}" rel="stylesheet"> <link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}"/>
<link href="{% static 'css/plugins/codemirror/ambiance.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/codemirror/codemirror.css' %}"
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet"> rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script> <link href="{% static 'css/plugins/codemirror/ambiance.css' %}"
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.exhide.min.js' %}"></script> 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/jquery.form.min.js' %}"></script>
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script> <script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
<script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script> <script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script>
<script src="{% static 'js/plugins/codemirror/codemirror.js' %}"></script> <script src="{% static 'js/plugins/codemirror/codemirror.js' %}"></script>
<script src="{% static 'js/plugins/codemirror/mode/shell/shell.js' %}"></script> <script src="{% static 'js/plugins/codemirror/mode/shell/shell.js' %}"></script>
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script> <script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<style type="text/css"> <style type="text/css">
.xterm .xterm-screen canvas { .xterm .xterm-screen canvas {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
padding: 10px; padding: 10px;
} }
.select2-container .select2-selection--single { .select2-container .select2-selection--single {
height: 34px; height: 34px;
} }
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="wrapper wrapper-content"> <div class="wrapper wrapper-content">
<div class="row"> <div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px"> <div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins"> <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"
<div class="file-manager "> style="padding-top: 0;padding-left: 1px">
<div id="assetTree" class="ztree"></div> <div class="file-manager ">
<div class="clearfix"></div> <div id="assetTree" class="ztree">
</div> {% trans 'Loading' %} ..
</div> </div>
</div> <div class="clearfix"></div>
</div> </div>
<div class="col-lg-9 animated fadeInRight" id="split-right"> </div>
<div class="tree-toggle"> </div>
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()"> </div>
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i> <div class="col-lg-9 animated fadeInRight" id="split-right">
</div> <div class="tree-toggle">
</div> <div class="btn btn-sm btn-primary tree-toggle-btn"
<div class="mail-box-header" style="padding-top: 5px;"> onclick="toggle()">
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" onsubmit="return execute()"> <i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
<div class="form-group"> </div>
<div id="term" style="height: 100%;width: 100%"></div> </div>
</div> <div class="mail-box-header" style="padding-top: 5px;">
<div class="row"> <form enctype="multipart/form-data" method="post"
<div class="col-lg-10"> class="form-horizontal" action=""
<div class="input-group" style="height: 100%; width: 100%"> onsubmit="return execute()">
<textarea class="form-control" id="command-text"></textarea> <div class="form-group">
</div> <div id="term"
</div> style="height: 100%;width: 100%"></div>
<div class="col-lg-2"> </div>
<select class="select2 form-control" id="system-users-select"> <div class="row">
{% for s in system_users %} <div class="col-lg-10">
{% if s.protocol == 'ssh' and s.login_mode == 'auto' %} <div class="input-group"
<option value="{{ s.id }}">{{ s }}</option> style="height: 100%; width: 100%">
{% endif %} <textarea class="form-control"
{% endfor %} id="command-text"></textarea>
</select> </div>
<button type="button" class="btn btn-primary btn-execute" style="margin-top: 30px; width: 100%">{% trans 'Go' %}</button> </div>
</div> <div class="col-lg-2">
</div> <select class="select2 form-control"
</form> id="system-users-select">
</div> {% for s in system_users %}
</div> {% if s.protocol == 'ssh' and s.login_mode == 'auto' %}
</div> <option value="{{ s.id }}">{{ s }}</option>
</div> {% endif %}
{% endfor %}
</select>
<button type="button"
class="btn btn-primary btn-execute"
style="margin-top: 30px; width: 100%">{% trans 'Go' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var zTree, show = 0; var zTree, show = 0;
var systemUserId = null; var systemUserId = null;
var url = 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) {
return null;
}
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
var elementStyle = window.getComputedStyle(term.element);
var elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
right: parseInt(elementStyle.getPropertyValue('padding-right')),
left: parseInt(elementStyle.getPropertyValue('padding-left'))
};
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
var elementPaddingHor = elementPadding.right + elementPadding.left;
var availableHeight = parentElementHeight - elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
var geometry = {
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
};
return geometry;
}
function fit(term) { function proposeGeometry(term) {
var geometry = proposeGeometry(term); if (!term.element.parentElement) {
if (geometry) { return null;
if (term.rows !== geometry.rows || term.cols !== geometry.cols) { }
term._core.renderer.clear(); var parentElementStyle = window.getComputedStyle(term.element.parentElement);
term.resize(geometry.cols, geometry.rows); var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
var elementStyle = window.getComputedStyle(term.element);
var elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
right: parseInt(elementStyle.getPropertyValue('padding-right')),
left: parseInt(elementStyle.getPropertyValue('padding-left'))
};
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
var elementPaddingHor = elementPadding.right + elementPadding.left;
var availableHeight = parentElementHeight - elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
var geometry = {
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
};
return geometry;
} }
}
}
function initTree() { function fit(term) {
if (systemUserId) { var geometry = proposeGeometry(term);
url = treeUrl + '&system_user=' + systemUserId if (geometry) {
} if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
else{ term._core.renderer.clear();
url = treeUrl term.resize(geometry.cols, geometry.rows);
} }
var setting = {
check: {
enable: true
},
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isCopy: true,
isMove: true
} }
},
callback: {
onCheck: onCheck
} }
};
function initTree() {
if (systemUserId) {
url = treeUrl + '&system_user=' + systemUserId
} else {
url = treeUrl
}
var setting = {
check: {
enable: true
},
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isCopy: true,
isMove: true
}
},
callback: {
onCheck: onCheck
}
};
$.get(url, function(data, status){ $.get(url, function (data, status) {
$.fn.zTree.init($("#assetTree"), setting, data); $.fn.zTree.init($("#assetTree"), setting, data);
zTree = $.fn.zTree.getZTreeObj("assetTree"); zTree = $.fn.zTree.getZTreeObj("assetTree");
rootNodeAddDom(zTree, function () { rootNodeAddDom(zTree, function () {
treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2'); treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
initTree(); initTree();
}); });
}); });
} }
function getSelectedAssetsNode() { function getSelectedAssetsNode() {
var nodes = zTree.getCheckedNodes(true); var nodes = zTree.getCheckedNodes(true);
var assetsNodeId = []; var assetsNodeId = [];
var assetsNode = []; var assetsNode = [];
nodes.forEach(function (node) { nodes.forEach(function (node) {
if (node.meta.type === 'asset' && !node.isHidden) { if (node.meta.type === 'asset' && !node.isHidden) {
var protocols = node.meta.asset.protocols; var protocols = node.meta.asset.protocols;
protocols.forEach(function (val) { protocols.forEach(function (val) {
if (assetsNodeId.indexOf(node.id) === -1 && val.indexOf("ssh") > -1) { if (assetsNodeId.indexOf(node.id) === -1 && val.indexOf("ssh") > -1) {
assetsNodeId.push(node.id); assetsNodeId.push(node.id);
assetsNode.push(node) assetsNode.push(node)
}
});
} }
}); });
return assetsNode;
} }
});
return assetsNode;
}
function onCheck(e, treeId, treeNode) { function onCheck(e, treeId, treeNode) {
var nodes = getSelectedAssetsNode(); var nodes = getSelectedAssetsNode();
var nodes_names = nodes.map(function (node) { var nodes_names = nodes.map(function (node) {
return node.name; return node.name;
}); });
var message = "{% trans 'Selected assets' %}" + ': '; var message = "{% trans 'Selected assets' %}" + ': ';
message += nodes_names.join(", "); message += nodes_names.join(", ");
message += "\r\n"; message += "\r\n";
message += "{% trans 'In total' %}" + ': ' + nodes_names.length + "个\r\n"; message += "{% trans 'In total' %}" + ': ' + nodes_names.length + "个\r\n";
term.clear(); term.clear();
term.write(message) term.write(message)
} }
function toggle() { function toggle() {
if (show === 0) { if (show === 0) {
$("#split-left").hide(500, function () { $("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-lg-12"); $("#split-right").attr("class", "col-lg-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x"); $("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
show = 1; show = 1;
}); });
} else { } else {
$("#split-right").attr("class", "col-lg-9"); $("#split-right").attr("class", "col-lg-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x"); $("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500); $("#split-left").show(500);
show = 0; show = 0;
} }
} }
var term = null; var term = null;
function initResultTerminal() { function initResultTerminal() {
term = new Terminal({ term = new Terminal({
cursorBlink: false, cursorBlink: false,
screenKeys: false, screenKeys: false,
fontFamily: 'monaco, Consolas, "Lucida Console", monospace', fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
fontSize: 14, fontSize: 14,
lineHeight: 1, lineHeight: 1,
rightClickSelectsWord: true, rightClickSelectsWord: true,
disableStdin: true, disableStdin: true,
theme: { theme: {
background: '#1f1b1b' background: '#1f1b1b'
}
});
term.open(document.getElementById('term'));
var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
fit(term);
term.write(msg)
} }
});
term.open(document.getElementById('term'));
var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
fit(term);
term.write(msg)
}
function wrapperError(msg) { function wrapperError(msg) {
return '\033[31m' + msg + '\033[0m' + '\r\n'; return '\033[31m' + msg + '\033[0m' + '\r\n';
} }
function execute() { function execute() {
if (!term) { if (!term) {
initResultTerminal() initResultTerminal()
} }
var size = 'rows=' + term.rows + '&cols=' + term.cols; var size = 'rows=' + term.rows + '&cols=' + term.cols;
var url = '{% url "api-ops:command-execution-list" %}?' + size; var url = '{% url "api-ops:command-execution-list" %}?' + size;
var run_as = systemUserId; var run_as = systemUserId;
var command = editor.getValue(); var command = editor.getValue();
var hosts = getSelectedAssetsNode().map(function (node) { var hosts = getSelectedAssetsNode().map(function (node) {
return node.id; return node.id;
}); });
if (hosts.length === 0) { if (hosts.length === 0) {
term.write(wrapperError("{% trans 'Unselected assets' %}")); term.write(wrapperError("{% trans 'Unselected assets' %}"));
return return
} }
if (!command) { if (!command) {
term.write(wrapperError("{% trans 'No input command' %}")); term.write(wrapperError("{% trans 'No input command' %}"));
return return
} }
if (!run_as) { if (!run_as) {
term.write(wrapperError("{% trans 'No system user was selected' %}")); term.write(wrapperError("{% trans 'No system user was selected' %}"));
return return
} }
var data = { var data = {
hosts: hosts, hosts: hosts,
run_as: run_as, run_as: run_as,
command: command command: command
}; };
var mark = ''; var mark = '';
var log_url = null; var log_url = null;
var end = false; var end = false;
var error = false; var error = false;
var int = null; var int = null;
var interval = 200; var interval = 200;
function writeExecutionOutput() { function writeExecutionOutput() {
if (!end) { if (!end) {
$.ajax({ $.ajax({
url: log_url + '?mark=' + mark, url: log_url + '?mark=' + mark,
method: "GET", method: "GET",
contentType: "application/json; charset=utf-8" contentType: "application/json; charset=utf-8"
}).done(function(data, textStatue, jqXHR) { }).done(function (data, textStatue, jqXHR) {
if (jqXHR.status === 203) { if (jqXHR.status === 203) {
error = true; error = true;
term.write('.'); term.write('.');
interval = 500; interval = 500;
} }
if (jqXHR.status === 200){ if (jqXHR.status === 200) {
term.write(data.data); term.write(data.data);
mark = data.mark; mark = data.mark;
if (data.end){ if (data.end) {
end = true; end = true;
window.clearInterval(int) window.clearInterval(int)
} }
}
})
} }
}) }
}
}
requestApi({ requestApi({
url: url, url: url,
body: JSON.stringify(data), body: JSON.stringify(data),
method: 'POST', method: 'POST',
flash_message: false, flash_message: false,
success: function (resp) { success: function (resp) {
var msg = "{% trans 'Pending' %}"; var msg = "{% trans 'Pending' %}";
term.write(msg + "...\r\n"); term.write(msg + "...\r\n");
log_url = resp.log_url; log_url = resp.log_url;
int = setInterval(function () { int = setInterval(function () {
writeExecutionOutput() writeExecutionOutput()
}, interval); }, interval);
}
});
return false;
} }
});
return false;
}
var editor; var editor;
$(document).ready(function(){ $(document).ready(function () {
systemUserId = $('#system-users-select').val(); systemUserId = $('#system-users-select').val();
$(".select2").select2({ $(".select2").select2({
dropdownAutoWidth : true, dropdownAutoWidth: true,
}).on('select2:select', function(evt) { }).on('select2:select', function (evt) {
var data = evt.params.data; var data = evt.params.data;
systemUserId = data.id; systemUserId = data.id;
initTree(); initTree();
}); });
editor = CodeMirror.fromTextArea(document.getElementById("command-text"), { editor = CodeMirror.fromTextArea(document.getElementById("command-text"), {
lineNumbers: true, lineNumbers: true,
lineWrapping: true, lineWrapping: true,
mode: "shell" mode: "shell"
}); });
editor.setSize(600, 100); editor.setSize(600, 100);
var charWidth = editor.defaultCharWidth(), basePadding = 4; var charWidth = editor.defaultCharWidth(), basePadding = 4;
editor.on("renderLine", function(cm, line, elt) { editor.on("renderLine", function (cm, line, elt) {
var off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth; var off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth;
elt.style.textIndent = "-" + off + "px"; elt.style.textIndent = "-" + off + "px";
elt.style.paddingLeft = (basePadding + off) + "px"; elt.style.paddingLeft = (basePadding + off) + "px";
}); });
editor.refresh(); editor.refresh();
initTree(); initTree();
initResultTerminal(); initResultTerminal();
}).on('click', '.btn-execute', function () { }).on('click', '.btn-execute', function () {
execute() execute()
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -9,6 +9,7 @@ from common.permissions import ( ...@@ -9,6 +9,7 @@ from common.permissions import (
PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor
) )
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from orgs.utils import tmp_to_root_org
from ..models import CommandExecution from ..models import CommandExecution
from ..forms import CommandExecutionForm from ..forms import CommandExecutionForm
...@@ -67,8 +68,9 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView): ...@@ -67,8 +68,9 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
def get_user_system_users(self): def get_user_system_users(self):
from perms.utils import AssetPermissionUtilV2 from perms.utils import AssetPermissionUtilV2
user = self.request.user user = self.request.user
util = AssetPermissionUtilV2(user) with tmp_to_root_org():
system_users = [s for s in util.get_system_users() if s.protocol == 'ssh'] util = AssetPermissionUtilV2(user)
system_users = [s for s in util.get_system_users() if s.protocol == 'ssh']
return system_users return system_users
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid import uuid
from hashlib import md5
from django.core.cache import cache
from django.db.models import Q 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 rest_framework.generics import get_object_or_404
from django.utils.translation import ugettext as _
from assets.utils import LabelFilterMixin 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 common.utils import get_logger
from orgs.utils import set_to_root_org 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 serializers
from .. import const
logger = get_logger(__name__) logger = get_logger(__name__)
__all__ = [ __all__ = [
'UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin',
'UserPermissionMixin', 'UserPermissionMixin',
] ]
...@@ -54,147 +45,6 @@ class UserPermissionMixin: ...@@ -54,147 +45,6 @@ class UserPermissionMixin:
return super().get_permissions() 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): class GrantAssetsMixin(LabelFilterMixin):
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetGrantedSerializer
......
# -*- coding: utf-8 -*-
#
from .common import *
from .user_permission_nodes import *
from .user_permission_assets import *
from .user_permission_nodes_with_assets import *
...@@ -9,29 +9,18 @@ from rest_framework.generics import ( ...@@ -9,29 +9,18 @@ from rest_framework.generics import (
) )
from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin
from common.tree import TreeNodeSerializer
from common.utils import get_logger from common.utils import get_logger
from ..utils import ( from ...utils import (
ParserNode, AssetPermissionUtilV2 AssetPermissionUtilV2
) )
from ..hands import User, Asset, Node, SystemUser, NodeSerializer from ...hands import User, Asset, SystemUser
from .. import serializers from ... import serializers
from ..models import Action from ...models import Action
from .mixin import UserPermissionMixin from .mixin import UserAssetPermissionMixin
logger = get_logger(__name__) logger = get_logger(__name__)
__all__ = [ __all__ = [
'UserGrantedAssetsApi',
'UserGrantedAssetsAsTreeApi',
'UserGrantedNodeAssetsApi',
'UserGrantedNodesApi',
'UserGrantedNodesAsTreeApi',
'UserGrantedNodesWithAssetsAsTreeApi',
'UserGrantedNodeChildrenApi',
'UserGrantedNodeChildrenAsTreeApi',
'UserGrantedNodeChildrenWithAssetsAsTreeApi',
'RefreshAssetPermissionCacheApi', 'RefreshAssetPermissionCacheApi',
'UserGrantedAssetSystemUsersApi', 'UserGrantedAssetSystemUsersApi',
'ValidateUserAssetPermissionApi', 'ValidateUserAssetPermissionApi',
...@@ -39,208 +28,8 @@ __all__ = [ ...@@ -39,208 +28,8 @@ __all__ = [
] ]
class UserAssetPermissionMixin(UserPermissionMixin): class GetUserAssetPermissionActionsApi(UserAssetPermissionMixin,
util = None RetrieveAPIView):
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,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.ActionsSerializer serializer_class = serializers.ActionsSerializer
...@@ -262,7 +51,8 @@ class GetUserAssetPermissionActionsApi(UserAssetPermissionMixin, RetrieveAPIView ...@@ -262,7 +51,8 @@ class GetUserAssetPermissionActionsApi(UserAssetPermissionMixin, RetrieveAPIView
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) system_user = get_object_or_404(SystemUser, id=system_id)
system_users_actions = self.util.get_asset_system_users_with_actions(asset) system_users_actions = self.util.get_asset_system_users_with_actions(
asset)
actions = system_users_actions.get(system_user) actions = system_users_actions.get(system_user)
return {"actions": actions} return {"actions": actions}
...@@ -274,7 +64,7 @@ class ValidateUserAssetPermissionApi(UserAssetPermissionMixin, APIView): ...@@ -274,7 +64,7 @@ class ValidateUserAssetPermissionApi(UserAssetPermissionMixin, APIView):
user_id = self.request.query_params.get('user_id', '') user_id = self.request.query_params.get('user_id', '')
user = get_object_or_404(User, id=user_id) user = get_object_or_404(User, id=user_id)
return user return user
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
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', '')
...@@ -289,7 +79,8 @@ class ValidateUserAssetPermissionApi(UserAssetPermissionMixin, APIView): ...@@ -289,7 +79,8 @@ class ValidateUserAssetPermissionApi(UserAssetPermissionMixin, APIView):
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) system_user = get_object_or_404(SystemUser, id=system_id)
system_users_actions = self.util.get_asset_system_users_with_actions(asset) system_users_actions = self.util.get_asset_system_users_with_actions(
asset)
actions = system_users_actions.get(system_user) actions = system_users_actions.get(system_user)
if action_name in Action.value_to_choices(actions): if action_name in Action.value_to_choices(actions):
return Response({'msg': True}, status=200) return Response({'msg': True}, status=200)
...@@ -312,7 +103,8 @@ class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView): ...@@ -312,7 +103,8 @@ class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView):
def get_queryset(self): def get_queryset(self):
asset_id = self.kwargs.get('asset_id') asset_id = self.kwargs.get('asset_id')
asset = get_object_or_404(Asset, id=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_with_actions = self.util.get_asset_system_users_with_actions(
asset)
system_users = [] system_users = []
for system_user, actions in system_users_with_actions.items(): for system_user, actions in system_users_with_actions.items():
system_user.actions = actions system_user.actions = actions
......
# -*- 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)
# -*- 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
# -*- 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
# -*- 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
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
<form> <form>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td colspan="2"> <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> </select>
</td> </td>
</tr> </tr>
...@@ -207,7 +207,7 @@ $(document).ready(function () { ...@@ -207,7 +207,7 @@ $(document).ready(function () {
var nodeListUrl = "{% url 'api-assets:node-list' %}"; var nodeListUrl = "{% url 'api-assets:node-list' %}";
nodesSelect2Init(".nodes-select2", nodeListUrl); 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'){ if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
...@@ -216,7 +216,7 @@ $(document).ready(function () { ...@@ -216,7 +216,7 @@ $(document).ready(function () {
}) })
}) })
.on('click', '.btn-add-assets', 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'); return $(this).attr('value');
}).get(); }).get();
if (assets_selected.length === 0) { if (assets_selected.length === 0) {
......
...@@ -4,6 +4,8 @@ import pickle ...@@ -4,6 +4,8 @@ import pickle
import threading import threading
from collections import defaultdict from collections import defaultdict
from functools import reduce from functools import reduce
from hashlib import md5
import json
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Q from django.db.models import Q
...@@ -60,29 +62,33 @@ def get_system_user_permissions(system_user): ...@@ -60,29 +62,33 @@ def get_system_user_permissions(system_user):
class AssetPermissionUtilCacheMixin: 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_ttl = settings.ASSETS_PERM_CACHE_TIME
user_tree_cache_enable = settings.ASSETS_PERM_CACHE_ENABLE user_tree_cache_enable = settings.ASSETS_PERM_CACHE_ENABLE
cache_policy = '0' cache_policy = '0'
obj_id = '' 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): def expire_user_tree_cache(self):
key = self.user_tree_cache_key.format(self.obj_id) cache.delete(self.cache_key)
cache.delete(key)
@classmethod @classmethod
def expire_all_user_tree_cache(cls): 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) cache.delete_pattern(key)
def set_user_tree_to_cache(self, user_tree): def set_user_tree_to_cache(self, user_tree):
data = pickle.dumps(user_tree) data = pickle.dumps(user_tree)
key = self.user_tree_cache_key.format(self.obj_id) cache.set(self.cache_key, data, self.user_tree_cache_ttl)
cache.set(key, data, self.user_tree_cache_ttl)
def get_user_tree_from_cache(self): def get_user_tree_from_cache(self):
key = self.user_tree_cache_key.format(self.obj_id) data = cache.get(self.cache_key)
data = cache.get(key)
if not data: if not data:
return None return None
user_tree = pickle.loads(data) user_tree = pickle.loads(data)
...@@ -129,6 +135,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): ...@@ -129,6 +135,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self._filter_id = 'None' # 当通过filter更改 permission是标记 self._filter_id = 'None' # 当通过filter更改 permission是标记
self.change_org_if_need() self.change_org_if_need()
self._user_tree = None self._user_tree = None
self._user_tree_filter_id = 'None'
self.full_tree = Node.tree() self.full_tree = Node.tree()
self.mutex = threading.Lock() self.mutex = threading.Lock()
...@@ -148,7 +155,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): ...@@ -148,7 +155,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
@timeit @timeit
def filter_permissions(self, **filters): def filter_permissions(self, **filters):
filters_json = json.dumps(filters, sort_keys=True)
self._permissions = self.permissions.filter(**filters) self._permissions = self.permissions.filter(**filters)
self._filter_id = md5(filters_json.encode()).hexdigest()
@property @property
def user_tree(self): def user_tree(self):
...@@ -282,15 +291,25 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): ...@@ -282,15 +291,25 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
parent=user_tree.root, 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 @timeit
def get_user_tree(self): def get_user_tree(self):
# 使用锁,保证多次获取tree的时候顺序执行,可以使用缓存 # 使用锁,保证多次获取tree的时候顺序执行,可以使用缓存
with self.mutex: with self.mutex:
if self._user_tree: user_tree = self.get_user_tree_from_local()
return self._user_tree if user_tree:
return user_tree
user_tree = self.get_user_tree_from_cache_if_need() user_tree = self.get_user_tree_from_cache_if_need()
if user_tree: if user_tree:
self._user_tree = user_tree self.set_user_tree_to_local(user_tree)
return user_tree return user_tree
user_tree = TreeService() user_tree = TreeService()
full_tree_root = self.full_tree.root_node() full_tree_root = self.full_tree.root_node()
...@@ -303,7 +322,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): ...@@ -303,7 +322,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self.parse_user_tree_to_full_tree(user_tree) self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(user_tree) self.add_empty_node_if_need(user_tree)
self.set_user_tree_to_cache_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 return user_tree
# Todo: 是否可以获取多个资产的系统用户 # Todo: 是否可以获取多个资产的系统用户
......
...@@ -22,7 +22,7 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): ...@@ -22,7 +22,7 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
instance.refresh_setting() instance.refresh_setting()
@receiver(django_ready, dispatch_uid="my_unique_identifier") @receiver(django_ready)
def monkey_patch_settings(sender, **kwargs): def monkey_patch_settings(sender, **kwargs):
logger.debug("Monkey patch settings") logger.debug("Monkey patch settings")
cache_key_prefix = '_SETTING_' cache_key_prefix = '_SETTING_'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment