mirror of https://github.com/jumpserver/jumpserver
[Update] 修改Permission
parent
8e9b3f134b
commit
8f699fa366
|
@ -7,15 +7,36 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from orgs.utils import current_org
|
||||
from perms.models import AssetPermission
|
||||
from assets.models import Asset, Node
|
||||
from ..models import AssetPermission, ActionFlag
|
||||
|
||||
__all__ = [
|
||||
'AssetPermissionForm',
|
||||
]
|
||||
|
||||
|
||||
class ActionField(forms.MultipleChoiceField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['choices'] = ActionFlag.CHOICES
|
||||
kwargs['initial'] = ActionFlag.ALL
|
||||
kwargs['label'] = _("Action")
|
||||
kwargs['widget'] = forms.CheckboxSelectMultiple()
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
value = super().to_python(value)
|
||||
return ActionFlag.choices_to_value(value)
|
||||
|
||||
def prepare_value(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
value = ActionFlag.value_to_choices(value)
|
||||
return value
|
||||
|
||||
|
||||
class AssetPermissionForm(OrgModelForm):
|
||||
action = ActionField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
users_field = self.fields.get('users')
|
||||
|
@ -32,10 +53,6 @@ class AssetPermissionForm(OrgModelForm):
|
|||
nodes_field = self.fields['nodes']
|
||||
nodes_field._queryset = Node.get_queryset()
|
||||
|
||||
def clean_action(self):
|
||||
actions = self.cleaned_data.get("action")
|
||||
return reduce(lambda x, y: x | y, actions)
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
exclude = (
|
||||
|
|
|
@ -36,10 +36,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='assetpermission',
|
||||
name='action',
|
||||
field=models.IntegerField(
|
||||
choices=[(255, 'All'), (1, 'Connect'), (2, 'Upload file'),
|
||||
(6, 'Upload download'), (4, 'Download file')],
|
||||
default=255, verbose_name='Action'),
|
||||
field=models.IntegerField(choices=[(255, 'All'), (1, 'Connect'), (2, 'Upload file'), (5, 'Upload download'), (4, 'Download file')], default=255, verbose_name='Action'),
|
||||
),
|
||||
migrations.RunPython(migrate_old_actions),
|
||||
]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import uuid
|
||||
from functools import reduce
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -37,17 +38,41 @@ class ActionFlag:
|
|||
CONNECT = 0b00000001
|
||||
UPLOAD = 0b00000010
|
||||
DOWNLOAD = 0b00000100
|
||||
UPDOWNLOAD = CONNECT | DOWNLOAD
|
||||
UPDOWNLOAD = UPLOAD | DOWNLOAD
|
||||
CONNECT_UPLOADOWN = CONNECT | UPDOWNLOAD
|
||||
ALL = 0b11111111
|
||||
NAME_MAP = {
|
||||
"connect": CONNECT,
|
||||
"upload": UPLOAD,
|
||||
"download": DOWNLOAD,
|
||||
"updownload": UPDOWNLOAD,
|
||||
"all": ALL,
|
||||
}
|
||||
|
||||
CHOICES = (
|
||||
(ALL, _('All')),
|
||||
(CONNECT, _('Connect')),
|
||||
(UPLOAD, _('Upload file')),
|
||||
(UPDOWNLOAD, _("Upload download")),
|
||||
(UPLOAD, _('Upload file')),
|
||||
(DOWNLOAD, _('Download file')),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def value_to_choices(cls, value):
|
||||
value = int(value)
|
||||
if value == cls.ALL:
|
||||
return [cls.ALL]
|
||||
elif value == cls.UPDOWNLOAD:
|
||||
return [cls.UPDOWNLOAD]
|
||||
elif value == cls.CONNECT_UPLOADOWN:
|
||||
return [cls.CONNECT, cls.UPDOWNLOAD]
|
||||
else:
|
||||
return [i for i in dict(cls.CHOICES) if i == i & int(value)]
|
||||
|
||||
@classmethod
|
||||
def choices_to_value(cls, value):
|
||||
return reduce(lambda x, y: int(x) | int(y), value)
|
||||
|
||||
|
||||
class AssetPermission(BasePermission):
|
||||
assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
|
||||
|
@ -60,13 +85,9 @@ class AssetPermission(BasePermission):
|
|||
unique_together = [('org_id', 'name')]
|
||||
verbose_name = _("Asset permission")
|
||||
|
||||
def get_all_assets(self):
|
||||
assets = set(self.assets.all())
|
||||
for node in self.nodes.all():
|
||||
_assets = node.get_all_assets()
|
||||
set_or_append_attr_bulk(_assets, 'inherit', node.value)
|
||||
assets.update(set(_assets))
|
||||
return assets
|
||||
@classmethod
|
||||
def get_queryset_with_prefetch(cls):
|
||||
return cls.objects.all().valid().prefetch_related('nodes', 'assets', 'system_users')
|
||||
|
||||
|
||||
class NodePermission(OrgModelMixin):
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from functools import reduce
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.fields import StringManyToManyField
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from perms.models import AssetPermission, Action
|
||||
from perms.models import AssetPermission, Action, ActionFlag
|
||||
from assets.models import Node
|
||||
from assets.serializers import AssetGrantedSerializer
|
||||
|
||||
|
@ -17,7 +18,28 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class ActionField(serializers.MultipleChoiceField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['choices'] = ActionFlag.CHOICES
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
return ActionFlag.value_to_choices(value)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
return ActionFlag.choices_to_value(data)
|
||||
|
||||
|
||||
class ActionDisplayField(ActionField):
|
||||
def to_representation(self, value):
|
||||
values = super().to_representation(value)
|
||||
choices = dict(ActionFlag.CHOICES)
|
||||
return [choices.get(i) for i in values]
|
||||
|
||||
|
||||
class AssetPermissionCreateUpdateSerializer(BulkOrgResourceModelSerializer):
|
||||
action = ActionField()
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
exclude = ('created_by', 'date_created')
|
||||
|
@ -29,7 +51,7 @@ class AssetPermissionListSerializer(BulkOrgResourceModelSerializer):
|
|||
assets = StringManyToManyField(many=True, read_only=True)
|
||||
nodes = StringManyToManyField(many=True, read_only=True)
|
||||
system_users = StringManyToManyField(many=True, read_only=True)
|
||||
action = serializers.IntegerField(read_only=True)
|
||||
action = ActionDisplayField()
|
||||
is_valid = serializers.BooleanField()
|
||||
is_expired = serializers.BooleanField()
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ var dateOptions = {
|
|||
format: 'YYYY-MM-DD HH:mm'
|
||||
}
|
||||
};
|
||||
var api_action = "{{ api_action }}";
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
closeOnSelect: false
|
||||
|
@ -147,21 +148,17 @@ $(document).ready(function () {
|
|||
.on("submit", "form", function (evt) {
|
||||
evt.preventDefault();
|
||||
var the_url = '{% url 'api-perms:asset-permission-list' %}';
|
||||
var redirect_to = '{% url "perms:asset-permission-list" %}';
|
||||
var method = "POST";
|
||||
{% if api_action == "update" %}
|
||||
the_url = '{% url 'api-perms:asset-permission-detail' pk=object.id %}';
|
||||
method = "PUT";
|
||||
{% endif %}
|
||||
var redirect_to = '{% url "perms:asset-permission-list" %}';
|
||||
var form = $("form");
|
||||
var data = form.serializeObject();
|
||||
console.log(data)
|
||||
var actions = data.action;
|
||||
var action = 0;
|
||||
for (i=0;i<actions.length;i++) {
|
||||
console.log(actions[i])
|
||||
action |= actions[i];
|
||||
}
|
||||
data.action = action;
|
||||
objectAttrsIsList(data, ['users', 'user_groups', 'system_users', 'nodes', 'assets']);
|
||||
objectAttrsIsList(data, ['users', 'user_groups', 'system_users', 'nodes', 'assets', 'actions']);
|
||||
objectAttrsIsDatetime(data, ['date_start', 'date_expired']);
|
||||
objectAttrsIsBool(data, ['is_active'])
|
||||
objectAttrsIsBool(data, ['is_active']);
|
||||
console.log(data)
|
||||
var props = {
|
||||
url: the_url,
|
||||
|
|
|
@ -122,8 +122,8 @@ function format(d) {
|
|||
if (d.system_users.length > 0) {
|
||||
data += makeLabel(["{% trans 'System user' %}", d.system_users.join(", ")])
|
||||
}
|
||||
if (d.actions.length > 0) {
|
||||
data += makeLabel(["{% trans 'Action' %}", d.actions.join(", ")])
|
||||
if (d.action.length > 0) {
|
||||
data += makeLabel(["{% trans 'Action' %}", d.action.join(", ")])
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
|
|
@ -103,11 +103,11 @@ def get_user_permissions(user, include_group=True):
|
|||
arg = Q(users=user) | Q(user_groups__in=groups)
|
||||
else:
|
||||
arg = Q(users=user)
|
||||
return AssetPermission.objects.valid().filter(arg)
|
||||
return AssetPermission.get_queryset_with_prefetch().filter(arg)
|
||||
|
||||
|
||||
def get_user_group_permissions(user_group):
|
||||
return AssetPermission.objects.valid().filter(
|
||||
return AssetPermission.get_queryset_with_prefetch().filter(
|
||||
user_groups=user_group
|
||||
)
|
||||
|
||||
|
@ -282,36 +282,55 @@ class AssetPermissionCacheMixin:
|
|||
cache.delete_pattern(key)
|
||||
|
||||
|
||||
class FlatPermissionQueryset:
|
||||
def __init__(self):
|
||||
self.queryset = defaultdict(list)
|
||||
|
||||
def add(self, permission):
|
||||
self.queryset[permission.id].append(permission)
|
||||
|
||||
def add_many(self, assets_or_nodes, system_users, actions):
|
||||
if any([assets_or_nodes, system_users, actions]):
|
||||
class FlatPermissionQueryset(set):
|
||||
def add_many(self, assets_or_nodes, system_users, action, rtp="asset"):
|
||||
print("Add many: {}-{}-{}".format(len(assets_or_nodes), len(system_users), action))
|
||||
if not any([assets_or_nodes, system_users, action]):
|
||||
return
|
||||
|
||||
iterable = itertools.product(assets_or_nodes, system_users, actions)
|
||||
iterable = itertools.product(assets_or_nodes, system_users, [action])
|
||||
for source, sysuser, action in iterable:
|
||||
permission = FlatPermission(source, sysuser, action)
|
||||
permission = FlatPermission(source, sysuser, action, rtp=rtp)
|
||||
print("ADDDDDDDDDDDDDDDd")
|
||||
self.add(permission)
|
||||
|
||||
def clean(self):
|
||||
pass
|
||||
def group_by_resource(self):
|
||||
resources = defaultdict(lambda: defaultdict(int))
|
||||
for i in self:
|
||||
resources[i.resource][i.system_user] |= i.action
|
||||
return resources
|
||||
|
||||
|
||||
class FlatPermission:
|
||||
def __init__(self, asset_or_node, system_user, action):
|
||||
self.id = asset_or_node.id
|
||||
self.source = asset_or_node
|
||||
def __init__(self, assets_or_node, system_user, action, rtp="asset"):
|
||||
self.id = "{}_{}_{}".format(assets_or_node.id, system_user.id, action)
|
||||
self.resource = assets_or_node
|
||||
self.resource_type = rtp
|
||||
self.system_user = system_user
|
||||
self.action = action
|
||||
|
||||
def __eq__(self, other):
|
||||
pass
|
||||
if self.id == other.id:
|
||||
return True
|
||||
# 资产不同
|
||||
if self.resource_type == "asset" and self.id != other.id:
|
||||
return False
|
||||
# 不是子节点
|
||||
elif self.resource_type == "node" and not other.resource.key.startswith(self.resource.key):
|
||||
return False
|
||||
# 系统用户优先级大于后者,则相同
|
||||
if self.system_user.priority > self.system_user.priority:
|
||||
return True
|
||||
# 如果系统用户不同,则不同
|
||||
elif self.system_user != other.system_user:
|
||||
return False
|
||||
# 如果action为与后的结果则相同
|
||||
if self.action == self.action | other.action:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.id)
|
||||
|
||||
|
||||
class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||
|
@ -355,33 +374,20 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
self._permissions = self.permissions.filter(**filters)
|
||||
self._filter_id = md5(filters_json.encode()).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
@timeit
|
||||
def _structured_system_user(system_users, actions):
|
||||
"""
|
||||
结构化系统用户
|
||||
:param system_users:
|
||||
:param actions:
|
||||
:return: {system_user1: {'actions': set(), }, }
|
||||
"""
|
||||
_attr = {'actions': set(actions)}
|
||||
_system_users = {system_user: _attr for system_user in system_users}
|
||||
return _system_users
|
||||
|
||||
@timeit
|
||||
def get_nodes_direct(self):
|
||||
"""
|
||||
返回用户/组授权规则直接关联的节点
|
||||
:return: {node1: {system_user1: {'actions': set()},}}
|
||||
"""
|
||||
nodes = FlatPermissionQueryset()
|
||||
permissions = self.permissions
|
||||
for perm in permissions:
|
||||
actions = perm.actions.all()
|
||||
queryset = FlatPermissionQueryset()
|
||||
for perm in self.permissions:
|
||||
actions = perm.action
|
||||
system_users = perm.system_users.all()
|
||||
_nodes = perm.nodes.all()
|
||||
nodes.add_many(_nodes, system_users, actions)
|
||||
return nodes
|
||||
nodes = perm.nodes.all()
|
||||
queryset.add_many(nodes, system_users, actions, rtp="nodes")
|
||||
print(queryset)
|
||||
return queryset.group_by_resource()
|
||||
|
||||
@timeit
|
||||
def get_assets_direct(self):
|
||||
|
@ -389,15 +395,14 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
返回用户授权规则直接关联的资产
|
||||
:return: {asset1: {system_user1: {'actions': set()},}}
|
||||
"""
|
||||
assets = defaultdict(dict)
|
||||
permissions = self.permissions.prefetch_related('assets', 'system_users')
|
||||
for perm in permissions:
|
||||
actions = perm.actions.all()
|
||||
for asset in perm.assets.all().valid().prefetch_related('nodes'):
|
||||
system_users = perm.system_users.filter(protocol__in=asset.protocols_name)
|
||||
system_users = self._structured_system_user(system_users, actions)
|
||||
assets[asset].update(system_users)
|
||||
return assets
|
||||
queryset = FlatPermissionQueryset()
|
||||
for perm in self.permissions:
|
||||
action = perm.action
|
||||
assets = perm.assets.all()
|
||||
system_users = perm.system_users.all()
|
||||
queryset.add_many(assets, system_users, action, rtp="assets")
|
||||
print(queryset)
|
||||
return queryset.group_by_resource()
|
||||
|
||||
@timeit
|
||||
def get_assets_without_cache(self):
|
||||
|
@ -408,27 +413,10 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
return self._assets
|
||||
assets = self.get_assets_direct()
|
||||
nodes = self.get_nodes_direct()
|
||||
# for node, system_users in nodes.items():
|
||||
# print(">>>>> Node<<<<<<<<<<<<: ", node.value)
|
||||
# _assets = list(node.get_all_valid_assets())
|
||||
# for asset in _assets:
|
||||
# for system_user, attr_dict in system_users.items():
|
||||
# if not asset.has_protocol(system_user.protocol):
|
||||
# continue
|
||||
# if system_user in assets[asset]:
|
||||
# actions = assets[asset][system_user]['actions']
|
||||
# attr_dict['actions'].update(actions)
|
||||
# system_users.update({system_user: attr_dict})
|
||||
# assets[asset].update(system_users)
|
||||
|
||||
__assets = defaultdict(set)
|
||||
for asset, system_users in assets.items():
|
||||
for system_user, attr_dict in system_users.items():
|
||||
setattr(system_user, 'actions', attr_dict['actions'])
|
||||
__assets[asset] = set(system_users.keys())
|
||||
|
||||
self._assets = __assets
|
||||
return self._assets
|
||||
print("++++++++++++++++++++++")
|
||||
print(assets)
|
||||
print("---------------------")
|
||||
print(nodes)
|
||||
|
||||
@timeit
|
||||
def get_nodes_with_assets_without_cache(self):
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.test import TestCase
|
||||
|
||||
from assets.models import Node, SystemUser
|
||||
from .asset_permission import FlatPermission
|
||||
from ..models import ActionFlag
|
||||
|
||||
|
||||
class TestFlatPermissionEqual(TestCase):
|
||||
def setUp(self):
|
||||
node1 = Node(value="parent", key="1:1")
|
||||
node2 = Node(value="child", key="1:1:1")
|
||||
|
||||
system_user1 = SystemUser(username="name1", name="name1", priority=20)
|
||||
system_user2 = SystemUser(username="name2", name="name2", priority=10)
|
||||
|
||||
action1 = ActionFlag.ALL
|
||||
action2 = ActionFlag.CONNECT
|
||||
action3 = ActionFlag.UPDOWNLOAD
|
||||
|
||||
perm1 = FlatPermission(node1, system_user1, action1)
|
||||
perm2 = FlatPermission(node2, system_user1, action1)
|
||||
perm3 = FlatPermission(node2, system_user2, action1)
|
||||
|
||||
self.groups = (
|
||||
(perm1, perm2, True),
|
||||
(perm1, perm3, True),
|
||||
)
|
||||
|
||||
def test_equal(self):
|
||||
for k, k2, wanted in self.groups:
|
||||
if (k == k2) != wanted:
|
||||
print("Not equal {} {}", k, k2)
|
||||
|
||||
|
|
@ -64,6 +64,7 @@ class AssetPermissionCreateView(PermissionsMixin, CreateView):
|
|||
context = {
|
||||
'app': _('Perms'),
|
||||
'action': _('Create asset permission'),
|
||||
'api_action': "create",
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -79,7 +80,8 @@ class AssetPermissionUpdateView(PermissionsMixin, UpdateView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Perms'),
|
||||
'action': _('Update asset permission')
|
||||
'action': _('Update asset permission'),
|
||||
'api_action': "update",
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
Loading…
Reference in New Issue