From 8f699fa366c86903e03c482fa9ba0031b54b88d4 Mon Sep 17 00:00:00 2001 From: ibuler <ibuler@qq.com> Date: Sun, 30 Jun 2019 20:10:34 +0800 Subject: [PATCH] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9Permission?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/forms/asset_permission.py | 27 +++- .../migrations/0006_auto_20190628_1921.py | 5 +- apps/perms/models/asset_permission.py | 39 ++++-- apps/perms/serializers/asset_permission.py | 26 +++- .../perms/asset_permission_create_update.html | 19 ++- .../perms/asset_permission_list.html | 4 +- apps/perms/utils/asset_permission.py | 126 ++++++++---------- apps/perms/utils/test_asset_permission.py | 36 +++++ apps/perms/views/asset_permission.py | 4 +- 9 files changed, 183 insertions(+), 103 deletions(-) create mode 100644 apps/perms/utils/test_asset_permission.py diff --git a/apps/perms/forms/asset_permission.py b/apps/perms/forms/asset_permission.py index d1fc76383..402e50da6 100644 --- a/apps/perms/forms/asset_permission.py +++ b/apps/perms/forms/asset_permission.py @@ -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 = ( diff --git a/apps/perms/migrations/0006_auto_20190628_1921.py b/apps/perms/migrations/0006_auto_20190628_1921.py index 764814e29..53afee94f 100644 --- a/apps/perms/migrations/0006_auto_20190628_1921.py +++ b/apps/perms/migrations/0006_auto_20190628_1921.py @@ -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), ] diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index a1c998daa..16d2889a1 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -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): diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index f1a1add19..b3f076d1d 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -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() diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html index b9945defe..af7157efc 100644 --- a/apps/perms/templates/perms/asset_permission_create_update.html +++ b/apps/perms/templates/perms/asset_permission_create_update.html @@ -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, diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html index b8c5b3af9..afd68648b 100644 --- a/apps/perms/templates/perms/asset_permission_list.html +++ b/apps/perms/templates/perms/asset_permission_list.html @@ -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 } diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index 3bd337004..9df402b6e 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -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): diff --git a/apps/perms/utils/test_asset_permission.py b/apps/perms/utils/test_asset_permission.py new file mode 100644 index 000000000..d4686f803 --- /dev/null +++ b/apps/perms/utils/test_asset_permission.py @@ -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) + + diff --git a/apps/perms/views/asset_permission.py b/apps/perms/views/asset_permission.py index 7acda41ef..e354e7479 100644 --- a/apps/perms/views/asset_permission.py +++ b/apps/perms/views/asset_permission.py @@ -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)