[Update] 修改Permission

pull/2874/head
ibuler 2019-06-30 20:10:34 +08:00
parent 8e9b3f134b
commit 8f699fa366
9 changed files with 183 additions and 103 deletions

View File

@ -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 = (

View File

@ -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),
]

View File

@ -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):

View File

@ -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()

View File

@ -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,

View File

@ -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
}

View File

@ -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):

View File

@ -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)

View File

@ -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)