mirror of https://github.com/jumpserver/jumpserver
				
				
				
			fix (#4680)
* perf(perms): 资产授权列表关联数据改为 `prefetch_related` * perf(perms): 优化一波 * dispatch_mapping_node_tasks.delay * perf: 在做一些优化 * perf: 再优化一波 * perf(perms): 授权更改节点慢的问题 * fix: 修改一处bug * perf(perms): ungrouped 资产数量计算方式 * fix: 修复dispatch data中的bug * fix(assets): add_nodes_assets_to_system_users celery task * fix: 修复ungrouped的bug * feat(nodes): 添加 favorite 节点 * feat(node): 添加 favorite api * fix: 修复clean keys的bug Co-authored-by: xinwen <coderWen@126.com> Co-authored-by: ibuler <ibuler@qq.com>pull/4709/head
							parent
							
								
									e3648d11b1
								
							
						
					
					
						commit
						d3be16ffe8
					
				| 
						 | 
				
			
			@ -85,7 +85,7 @@ class FilterAssetByNodeMixin:
 | 
			
		|||
        show_current_asset_arg = request.query_params.get('show_current_asset')
 | 
			
		||||
        if show_current_asset_arg is not None:
 | 
			
		||||
            return show_current_asset_arg != '1'
 | 
			
		||||
        return query_all_arg == '1'
 | 
			
		||||
        return query_all_arg != '0'
 | 
			
		||||
 | 
			
		||||
    @lazyproperty
 | 
			
		||||
    def node(self):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,6 +47,10 @@ class AssetManager(OrgManager):
 | 
			
		|||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AssetOrgManager(OrgManager):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AssetQuerySet(models.QuerySet):
 | 
			
		||||
    def active(self):
 | 
			
		||||
        return self.filter(is_active=True)
 | 
			
		||||
| 
						 | 
				
			
			@ -226,6 +230,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
 | 
			
		|||
    comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
 | 
			
		||||
 | 
			
		||||
    objects = AssetManager.from_queryset(AssetQuerySet)()
 | 
			
		||||
    org_objects = AssetOrgManager.from_queryset(AssetQuerySet)()
 | 
			
		||||
    _connectivity = None
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,3 +18,11 @@ class FavoriteAsset(CommonModelMixin):
 | 
			
		|||
    @classmethod
 | 
			
		||||
    def get_user_favorite_assets_id(cls, user):
 | 
			
		||||
        return cls.objects.filter(user=user).values_list('asset', flat=True)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_user_favorite_assets(cls, user):
 | 
			
		||||
        from assets.models import Asset
 | 
			
		||||
        from perms.utils.user_asset_permission import get_user_granted_all_assets
 | 
			
		||||
        asset_ids = get_user_granted_all_assets(user).values_list('id', flat=True)
 | 
			
		||||
        query_name = cls.asset.field.related_query_name()
 | 
			
		||||
        return Asset.org_objects.filter(**{f'{query_name}__user_id': user.id}, id__in=asset_ids).distinct()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,16 +41,16 @@ class FamilyMixin:
 | 
			
		|||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def clean_children_keys(nodes_keys):
 | 
			
		||||
        nodes_keys = sorted(list(nodes_keys), key=lambda x: (len(x), x))
 | 
			
		||||
        sort_key = lambda k: [int(i) for i in k.split(':')]
 | 
			
		||||
        nodes_keys = sorted(list(nodes_keys), key=sort_key)
 | 
			
		||||
 | 
			
		||||
        nodes_keys_clean = []
 | 
			
		||||
        for key in nodes_keys[::-1]:
 | 
			
		||||
            found = False
 | 
			
		||||
            for k in nodes_keys:
 | 
			
		||||
                if key.startswith(k + ':'):
 | 
			
		||||
                    found = True
 | 
			
		||||
                    break
 | 
			
		||||
            if not found:
 | 
			
		||||
                nodes_keys_clean.append(key)
 | 
			
		||||
        base_key = ''
 | 
			
		||||
        for key in nodes_keys:
 | 
			
		||||
            if key.startswith(base_key + ':'):
 | 
			
		||||
                continue
 | 
			
		||||
            nodes_keys_clean.append(key)
 | 
			
		||||
            base_key = key
 | 
			
		||||
        return nodes_keys_clean
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
| 
						 | 
				
			
			@ -213,26 +213,29 @@ class NodeAssetsMixin:
 | 
			
		|||
    key = ''
 | 
			
		||||
    id = None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def clear_all_nodes_assets_amount(cls):
 | 
			
		||||
        nodes = cls.objects.all()
 | 
			
		||||
        for node in nodes:
 | 
			
		||||
            count = node.get_all_assets().count()
 | 
			
		||||
 | 
			
		||||
    def get_all_assets(self):
 | 
			
		||||
        from .asset import Asset
 | 
			
		||||
        if self.is_org_root():
 | 
			
		||||
            return Asset.objects.filter(org_id=self.org_id)
 | 
			
		||||
 | 
			
		||||
        q = Q(nodes__key__startswith=self.key) | Q(nodes__key=self.key)
 | 
			
		||||
        q = Q(nodes__key__startswith=f'{self.key}:') | Q(nodes__key=self.key)
 | 
			
		||||
        return Asset.objects.filter(q).distinct()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_node_all_assets_by_key_v2(cls, key):
 | 
			
		||||
        # 最初的写法是:
 | 
			
		||||
        #   Asset.objects.filter(Q(nodes__key__startswith=f'{node.key}:') | Q(nodes__id=node.id))
 | 
			
		||||
        #   可是 startswith 会导致表关联时 Asset 索引失效
 | 
			
		||||
        from .asset import Asset
 | 
			
		||||
        node_ids = cls.objects.filter(
 | 
			
		||||
            Q(key__startswith=f'{key}:') |
 | 
			
		||||
            Q(key=key)
 | 
			
		||||
        ).values_list('id', flat=True).distinct()
 | 
			
		||||
        assets = Asset.objects.filter(
 | 
			
		||||
            nodes__id__in=list(node_ids)
 | 
			
		||||
        ).distinct()
 | 
			
		||||
        return assets
 | 
			
		||||
 | 
			
		||||
    def get_assets(self):
 | 
			
		||||
        from .asset import Asset
 | 
			
		||||
        if self.is_org_root():
 | 
			
		||||
            assets = Asset.objects.filter(Q(nodes=self) | Q(nodes__isnull=True))
 | 
			
		||||
        else:
 | 
			
		||||
            assets = Asset.objects.filter(nodes=self)
 | 
			
		||||
        assets = Asset.objects.filter(nodes=self)
 | 
			
		||||
        return assets.distinct()
 | 
			
		||||
 | 
			
		||||
    def get_valid_assets(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -241,51 +244,54 @@ class NodeAssetsMixin:
 | 
			
		|||
    def get_all_valid_assets(self):
 | 
			
		||||
        return self.get_all_assets().valid()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _get_nodes_all_assets(cls, nodes_keys):
 | 
			
		||||
        """
 | 
			
		||||
        当节点比较多的时候,这种正则方式性能差极了
 | 
			
		||||
        :param nodes_keys:
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
        from .asset import Asset
 | 
			
		||||
        nodes_keys = cls.clean_children_keys(nodes_keys)
 | 
			
		||||
        nodes_children_pattern = set()
 | 
			
		||||
        for key in nodes_keys:
 | 
			
		||||
            children_pattern = cls.get_node_all_children_key_pattern(key)
 | 
			
		||||
            nodes_children_pattern.add(children_pattern)
 | 
			
		||||
        pattern = '|'.join(nodes_children_pattern)
 | 
			
		||||
        return Asset.objects.filter(nodes__key__regex=pattern).distinct()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_nodes_all_assets_ids(cls, nodes_keys):
 | 
			
		||||
        nodes_keys = cls.clean_children_keys(nodes_keys)
 | 
			
		||||
        assets_ids = set()
 | 
			
		||||
        for key in nodes_keys:
 | 
			
		||||
            node_assets_ids = cls.tree().all_assets(key)
 | 
			
		||||
            assets_ids.update(set(node_assets_ids))
 | 
			
		||||
        assets_ids = cls.get_nodes_all_assets(nodes_keys).values_list('id', flat=True)
 | 
			
		||||
        return assets_ids
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_nodes_all_assets(cls, nodes_keys, extra_assets_ids=None):
 | 
			
		||||
        from .asset import Asset
 | 
			
		||||
        nodes_keys = cls.clean_children_keys(nodes_keys)
 | 
			
		||||
        assets_ids = cls.get_nodes_all_assets_ids(nodes_keys)
 | 
			
		||||
        q = Q()
 | 
			
		||||
        node_ids = ()
 | 
			
		||||
        for key in nodes_keys:
 | 
			
		||||
            q |= Q(key__startswith=f'{key}:')
 | 
			
		||||
            q |= Q(key=key)
 | 
			
		||||
        if q:
 | 
			
		||||
            node_ids = Node.objects.filter(q).distinct().values_list('id', flat=True)
 | 
			
		||||
 | 
			
		||||
        q = Q(nodes__id__in=list(node_ids))
 | 
			
		||||
        if extra_assets_ids:
 | 
			
		||||
            assets_ids.update(set(extra_assets_ids))
 | 
			
		||||
        return Asset.objects.filter(id__in=assets_ids)
 | 
			
		||||
            q |= Q(id__in=extra_assets_ids)
 | 
			
		||||
        if q:
 | 
			
		||||
            return Asset.org_objects.filter(q).distinct()
 | 
			
		||||
        else:
 | 
			
		||||
            return Asset.objects.none()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SomeNodesMixin:
 | 
			
		||||
    key = ''
 | 
			
		||||
    default_key = '1'
 | 
			
		||||
    default_value = 'Default'
 | 
			
		||||
    ungrouped_key = '-10'
 | 
			
		||||
    ungrouped_value = _('ungrouped')
 | 
			
		||||
    empty_key = '-11'
 | 
			
		||||
    empty_value = _("empty")
 | 
			
		||||
    favorite_key = '-12'
 | 
			
		||||
    favorite_value = _("favorite")
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def default_node(cls):
 | 
			
		||||
        with tmp_to_org(Organization.default()):
 | 
			
		||||
            defaults = {'value': cls.default_value}
 | 
			
		||||
            try:
 | 
			
		||||
                obj, created = cls.objects.get_or_create(
 | 
			
		||||
                    defaults=defaults, key=cls.default_key,
 | 
			
		||||
                )
 | 
			
		||||
            except IntegrityError as e:
 | 
			
		||||
                logger.error("Create default node failed: {}".format(e))
 | 
			
		||||
                cls.modify_other_org_root_node_key()
 | 
			
		||||
                obj, created = cls.objects.get_or_create(
 | 
			
		||||
                    defaults=defaults, key=cls.default_key,
 | 
			
		||||
                )
 | 
			
		||||
            return obj
 | 
			
		||||
 | 
			
		||||
    def is_default_node(self):
 | 
			
		||||
        return self.key == self.default_key
 | 
			
		||||
| 
						 | 
				
			
			@ -320,51 +326,15 @@ class SomeNodesMixin:
 | 
			
		|||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def org_root(cls):
 | 
			
		||||
        root = cls.objects.filter(key__regex=r'^[0-9]+$')
 | 
			
		||||
        root = cls.objects.filter(parent_key='').exclude(key__startswith='-')
 | 
			
		||||
        if root:
 | 
			
		||||
            return root[0]
 | 
			
		||||
        else:
 | 
			
		||||
            return cls.create_org_root_node()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def ungrouped_node(cls):
 | 
			
		||||
        with tmp_to_org(Organization.system()):
 | 
			
		||||
            defaults = {'value': cls.ungrouped_value}
 | 
			
		||||
            obj, created = cls.objects.get_or_create(
 | 
			
		||||
                defaults=defaults, key=cls.ungrouped_key
 | 
			
		||||
            )
 | 
			
		||||
            return obj
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def default_node(cls):
 | 
			
		||||
        with tmp_to_org(Organization.default()):
 | 
			
		||||
            defaults = {'value': cls.default_value}
 | 
			
		||||
            try:
 | 
			
		||||
                obj, created = cls.objects.get_or_create(
 | 
			
		||||
                    defaults=defaults, key=cls.default_key,
 | 
			
		||||
                )
 | 
			
		||||
            except IntegrityError as e:
 | 
			
		||||
                logger.error("Create default node failed: {}".format(e))
 | 
			
		||||
                cls.modify_other_org_root_node_key()
 | 
			
		||||
                obj, created = cls.objects.get_or_create(
 | 
			
		||||
                    defaults=defaults, key=cls.default_key,
 | 
			
		||||
                )
 | 
			
		||||
            return obj
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def favorite_node(cls):
 | 
			
		||||
        with tmp_to_org(Organization.system()):
 | 
			
		||||
            defaults = {'value': cls.favorite_value}
 | 
			
		||||
            obj, created = cls.objects.get_or_create(
 | 
			
		||||
                defaults=defaults, key=cls.favorite_key
 | 
			
		||||
            )
 | 
			
		||||
            return obj
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def initial_some_nodes(cls):
 | 
			
		||||
        cls.default_node()
 | 
			
		||||
        cls.ungrouped_node()
 | 
			
		||||
        cls.favorite_node()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def modify_other_org_root_node_key(cls):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,16 +17,13 @@ class AssetLimitOffsetPagination(LimitOffsetPagination):
 | 
			
		|||
        exclude_query_params = {
 | 
			
		||||
            self.limit_query_param,
 | 
			
		||||
            self.offset_query_param,
 | 
			
		||||
            'node', 'all', 'show_current_asset'
 | 
			
		||||
            'node', 'all', 'show_current_asset',
 | 
			
		||||
            'node_id', 'display', 'draw',
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        has_filter = False
 | 
			
		||||
        for k, v in self._request.query_params.items():
 | 
			
		||||
            if k not in exclude_query_params and v is not None:
 | 
			
		||||
                has_filter = True
 | 
			
		||||
                break
 | 
			
		||||
        if has_filter:
 | 
			
		||||
            return super().get_count(queryset)
 | 
			
		||||
                return super().get_count(queryset)
 | 
			
		||||
 | 
			
		||||
        is_query_all = self._view.is_query_node_all_assets
 | 
			
		||||
        if is_query_all:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -115,6 +115,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
 | 
			
		|||
    def setup_eager_loading(cls, queryset):
 | 
			
		||||
        """ Perform necessary eager loading of data. """
 | 
			
		||||
        queryset = queryset.select_related('admin_user', 'domain', 'platform')
 | 
			
		||||
        queryset = queryset.prefetch_related('nodes', 'labels')
 | 
			
		||||
        return queryset
 | 
			
		||||
 | 
			
		||||
    def compatible_with_old_protocol(self, validated_data):
 | 
			
		||||
| 
						 | 
				
			
			@ -152,7 +153,7 @@ class AssetDisplaySerializer(AssetSerializer):
 | 
			
		|||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setup_eager_loading(cls, queryset):
 | 
			
		||||
        """ Perform necessary eager loading of data. """
 | 
			
		||||
        queryset = super().setup_eager_loading(queryset)
 | 
			
		||||
        queryset = queryset\
 | 
			
		||||
            .annotate(admin_user_username=F('admin_user__username'))
 | 
			
		||||
        return queryset
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,3 +9,4 @@ from .gather_asset_users import *
 | 
			
		|||
from .gather_asset_hardware_info import *
 | 
			
		||||
from .push_system_user import *
 | 
			
		||||
from .system_user_connectivity import *
 | 
			
		||||
from .nodes_amount import *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
from celery import shared_task
 | 
			
		||||
 | 
			
		||||
from assets.utils import check_node_assets_amount
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
from common.utils.timezone import now
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__file__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task()
 | 
			
		||||
def check_node_assets_amount_celery_task():
 | 
			
		||||
    logger.info(f'>>> {now()} begin check_node_assets_amount_celery_task ...')
 | 
			
		||||
    check_node_assets_amount()
 | 
			
		||||
    logger.info(f'>>> {now()} end check_node_assets_amount_celery_task ...')
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +1,8 @@
 | 
			
		|||
# ~*~ coding: utf-8 ~*~
 | 
			
		||||
#
 | 
			
		||||
from treelib import Tree
 | 
			
		||||
from treelib.exceptions import NodeIDAbsentError
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from copy import deepcopy
 | 
			
		||||
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
 | 
			
		||||
from common.utils import get_logger, timeit, lazyproperty
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
from .models import Asset, Node
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +16,7 @@ def check_node_assets_amount():
 | 
			
		|||
        ).distinct().count()
 | 
			
		||||
 | 
			
		||||
        if node.assets_amount != assets_amount:
 | 
			
		||||
            print(f'<Node:{node.key}> wrong assets amount '
 | 
			
		||||
            print(f'>>> <Node:{node.key}> wrong assets amount '
 | 
			
		||||
                  f'{node.assets_amount} right is {assets_amount}')
 | 
			
		||||
            node.assets_amount = assets_amount
 | 
			
		||||
            node.save()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -257,6 +257,7 @@ class Config(dict):
 | 
			
		|||
        'SYSLOG_FACILITY': 'user',
 | 
			
		||||
        'SYSLOG_SOCKTYPE': 2,
 | 
			
		||||
        'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
 | 
			
		||||
        'PERM_EXPIRED_CHECK_PERIODIC': 60,
 | 
			
		||||
        'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
 | 
			
		||||
        'FLOWER_URL': "127.0.0.1:5555",
 | 
			
		||||
        'DEFAULT_ORG_SHOW_ALL_USERS': True,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -75,6 +75,7 @@ BACKEND_ASSET_USER_AUTH_VAULT = False
 | 
			
		|||
 | 
			
		||||
DEFAULT_ORG_SHOW_ALL_USERS = CONFIG.DEFAULT_ORG_SHOW_ALL_USERS
 | 
			
		||||
PERM_SINGLE_ASSET_TO_UNGROUP_NODE = CONFIG.PERM_SINGLE_ASSET_TO_UNGROUP_NODE
 | 
			
		||||
PERM_EXPIRED_CHECK_PERIODIC = CONFIG.PERM_EXPIRED_CHECK_PERIODIC
 | 
			
		||||
WINDOWS_SSH_DEFAULT_SHELL = CONFIG.WINDOWS_SSH_DEFAULT_SHELL
 | 
			
		||||
FLOWER_URL = CONFIG.FLOWER_URL
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import os
 | 
			
		|||
 | 
			
		||||
from kombu import Exchange, Queue
 | 
			
		||||
from celery import Celery
 | 
			
		||||
from celery.schedules import crontab
 | 
			
		||||
 | 
			
		||||
# set the default Django settings module for the 'celery' program.
 | 
			
		||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jumpserver.settings')
 | 
			
		||||
| 
						 | 
				
			
			@ -28,3 +29,16 @@ configs["CELERY_ROUTES"] = {
 | 
			
		|||
app.namespace = 'CELERY'
 | 
			
		||||
app.conf.update(configs)
 | 
			
		||||
app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS])
 | 
			
		||||
 | 
			
		||||
app.conf.beat_schedule = {
 | 
			
		||||
    'check-asset-permission-expired': {
 | 
			
		||||
        'task': 'perms.tasks.check_asset_permission_expired',
 | 
			
		||||
        'schedule': settings.PERM_EXPIRED_CHECK_PERIODIC,
 | 
			
		||||
        'args': ()
 | 
			
		||||
    },
 | 
			
		||||
    'check-node-assets-amount': {
 | 
			
		||||
        'task': 'assets.tasks.nodes_amount.check_node_assets_amount_celery_task',
 | 
			
		||||
        'schedule': crontab(minute=0, hour=0),
 | 
			
		||||
        'args': ()
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -111,7 +111,7 @@ class AssetPermissionAllAssetListApi(generics.ListAPIView):
 | 
			
		|||
            asset_q |= Q(nodes__key__startswith=f'{key}:')
 | 
			
		||||
            asset_q |= Q(nodes__key=key)
 | 
			
		||||
 | 
			
		||||
        assets = Asset.objects.filter(asset_q).only(*self.serializer_class.Meta.only_fields)
 | 
			
		||||
        assets = Asset.objects.filter(asset_q).only(*self.serializer_class.Meta.only_fields).distinct()
 | 
			
		||||
        return assets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,45 +1,41 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
 | 
			
		||||
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
 | 
			
		||||
from common.utils import lazyproperty
 | 
			
		||||
from rest_framework.generics import get_object_or_404
 | 
			
		||||
 | 
			
		||||
from users.models import User
 | 
			
		||||
from perms.models import UserGrantedMappingNode
 | 
			
		||||
from common.exceptions import JMSObjectDoesNotExist
 | 
			
		||||
from perms.async_tasks.mapping_node_task import submit_update_mapping_node_task_for_user
 | 
			
		||||
from ...hands import Node
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserGrantedNodeDispatchMixin:
 | 
			
		||||
class UserNodeGrantStatusDispatchMixin:
 | 
			
		||||
 | 
			
		||||
    def submit_update_mapping_node_task(self, user):
 | 
			
		||||
        submit_update_mapping_node_task_for_user(user)
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_mapping_node_by_key(key):
 | 
			
		||||
        return UserGrantedMappingNode.objects.get(key=key)
 | 
			
		||||
 | 
			
		||||
    def dispatch_node_process(self, key, mapping_node: UserGrantedMappingNode, node: Node = None):
 | 
			
		||||
        if mapping_node is None:
 | 
			
		||||
            ancestor_keys = Node.get_node_ancestor_keys(key)
 | 
			
		||||
            granted = UserGrantedMappingNode.objects.filter(key__in=ancestor_keys, granted=True).exists()
 | 
			
		||||
            if not granted:
 | 
			
		||||
                raise JMSObjectDoesNotExist(object_name=Node._meta.object_name)
 | 
			
		||||
            queryset = self.on_granted_node(key, mapping_node, node)
 | 
			
		||||
    def dispatch_get_data(self, key, user):
 | 
			
		||||
        status = UserGrantedMappingNode.get_node_granted_status(key, user)
 | 
			
		||||
        if status == UserGrantedMappingNode.GRANTED_DIRECT:
 | 
			
		||||
            return self.get_data_on_node_direct_granted(key)
 | 
			
		||||
        elif status == UserGrantedMappingNode.GRANTED_INDIRECT:
 | 
			
		||||
            return self.get_data_on_node_indirect_granted(key)
 | 
			
		||||
        else:
 | 
			
		||||
            if mapping_node.granted:
 | 
			
		||||
                # granted_node
 | 
			
		||||
                queryset = self.on_granted_node(key, mapping_node, node)
 | 
			
		||||
            else:
 | 
			
		||||
                queryset = self.on_ungranted_node(key, mapping_node, node)
 | 
			
		||||
        return queryset
 | 
			
		||||
            return self.get_data_on_node_not_granted(key)
 | 
			
		||||
 | 
			
		||||
    def on_granted_node(self, key, mapping_node: UserGrantedMappingNode, node: Node = None):
 | 
			
		||||
    def get_data_on_node_direct_granted(self, key):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def on_ungranted_node(self, key, mapping_node: UserGrantedMappingNode, node: Node = None):
 | 
			
		||||
    def get_data_on_node_indirect_granted(self, key):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def get_data_on_node_not_granted(self, key):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ForAdminMixin:
 | 
			
		||||
    permission_classes = (IsOrgAdminOrAppUser,)
 | 
			
		||||
    kwargs: dict
 | 
			
		||||
 | 
			
		||||
    @lazyproperty
 | 
			
		||||
    def user(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +45,7 @@ class ForAdminMixin:
 | 
			
		|||
 | 
			
		||||
class ForUserMixin:
 | 
			
		||||
    permission_classes = (IsValidUser,)
 | 
			
		||||
    request: Request
 | 
			
		||||
 | 
			
		||||
    @lazyproperty
 | 
			
		||||
    def user(self):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,39 +1,32 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.utils.decorators import method_decorator
 | 
			
		||||
from perms.api.user_permission.mixin import UserGrantedNodeDispatchMixin
 | 
			
		||||
from perms.api.user_permission.mixin import UserNodeGrantStatusDispatchMixin
 | 
			
		||||
from rest_framework.generics import ListAPIView
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
from assets.api.mixin import SerializeToTreeNodeMixin
 | 
			
		||||
from common.utils import get_object_or_none
 | 
			
		||||
from users.models import User
 | 
			
		||||
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
from ...hands import Node
 | 
			
		||||
from ... import serializers
 | 
			
		||||
from perms.models import UserGrantedMappingNode
 | 
			
		||||
from perms.utils.user_node_tree import get_node_all_granted_assets
 | 
			
		||||
from perms.pagination import GrantedAssetLimitOffsetPagination
 | 
			
		||||
from assets.models import Asset
 | 
			
		||||
from assets.models import Asset, Node, FavoriteAsset
 | 
			
		||||
from orgs.utils import tmp_to_root_org
 | 
			
		||||
from ... import serializers
 | 
			
		||||
from ...utils.user_asset_permission import (
 | 
			
		||||
    get_node_all_granted_assets, get_user_direct_granted_assets,
 | 
			
		||||
    get_user_granted_all_assets
 | 
			
		||||
)
 | 
			
		||||
from .mixin import ForAdminMixin, ForUserMixin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__name__)
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    'UserDirectGrantedAssetsForAdminApi', 'MyAllAssetsAsTreeApi',
 | 
			
		||||
    'UserGrantedNodeAssetsForAdminApi', 'MyDirectGrantedAssetsApi',
 | 
			
		||||
    'UserDirectGrantedAssetsAsTreeForAdminApi', 'MyGrantedNodeAssetsApi',
 | 
			
		||||
    'MyUngroupAssetsAsTreeApi',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@method_decorator(tmp_to_root_org(), name='list')
 | 
			
		||||
class UserDirectGrantedAssetsApi(ListAPIView):
 | 
			
		||||
    """
 | 
			
		||||
    用户直接授权的资产的列表,也就是授权规则上直接授权的资产,并非是来自节点的
 | 
			
		||||
    """
 | 
			
		||||
    serializer_class = serializers.AssetGrantedSerializer
 | 
			
		||||
    only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
 | 
			
		||||
    filter_fields = ['hostname', 'ip', 'id', 'comment']
 | 
			
		||||
| 
						 | 
				
			
			@ -41,17 +34,32 @@ class UserDirectGrantedAssetsApi(ListAPIView):
 | 
			
		|||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        user = self.user
 | 
			
		||||
        assets = get_user_direct_granted_assets(user)\
 | 
			
		||||
            .prefetch_related('platform')\
 | 
			
		||||
            .only(*self.only_fields)
 | 
			
		||||
        return assets
 | 
			
		||||
 | 
			
		||||
        return Asset.objects.filter(
 | 
			
		||||
            Q(granted_by_permissions__users=user) |
 | 
			
		||||
            Q(granted_by_permissions__user_groups__users=user)
 | 
			
		||||
        ).distinct().only(
 | 
			
		||||
            *self.only_fields
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@method_decorator(tmp_to_root_org(), name='list')
 | 
			
		||||
class UserFavoriteGrantedAssetsApi(ListAPIView):
 | 
			
		||||
    serializer_class = serializers.AssetGrantedSerializer
 | 
			
		||||
    only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
 | 
			
		||||
    filter_fields = ['hostname', 'ip', 'id', 'comment']
 | 
			
		||||
    search_fields = ['hostname', 'ip', 'comment']
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        user = self.user
 | 
			
		||||
        assets = FavoriteAsset.get_user_favorite_assets(user)\
 | 
			
		||||
            .prefetch_related('platform')\
 | 
			
		||||
            .only(*self.only_fields)
 | 
			
		||||
        return assets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@method_decorator(tmp_to_root_org(), name='list')
 | 
			
		||||
class AssetsAsTreeMixin(SerializeToTreeNodeMixin):
 | 
			
		||||
    """
 | 
			
		||||
    将 资产 序列化成树的结构返回
 | 
			
		||||
    """
 | 
			
		||||
    def list(self, request, *args, **kwargs):
 | 
			
		||||
        queryset = self.filter_queryset(self.get_queryset())
 | 
			
		||||
        data = self.serialize_assets(queryset, None)
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +74,14 @@ class MyDirectGrantedAssetsApi(ForUserMixin, UserDirectGrantedAssetsApi):
 | 
			
		|||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserFavoriteGrantedAssetsForAdminApi(ForAdminMixin, UserFavoriteGrantedAssetsApi):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyFavoriteGrantedAssetsApi(ForUserMixin, UserFavoriteGrantedAssetsApi):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@method_decorator(tmp_to_root_org(), name='list')
 | 
			
		||||
class UserDirectGrantedAssetsAsTreeForAdminApi(ForAdminMixin, AssetsAsTreeMixin, UserDirectGrantedAssetsApi):
 | 
			
		||||
    pass
 | 
			
		||||
| 
						 | 
				
			
			@ -85,27 +101,8 @@ class UserAllGrantedAssetsApi(ListAPIView):
 | 
			
		|||
    only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        user = self.user
 | 
			
		||||
 | 
			
		||||
        granted_node_keys = Node.objects.filter(
 | 
			
		||||
            Q(granted_by_permissions__users=user) |
 | 
			
		||||
            Q(granted_by_permissions__user_groups__users=user)
 | 
			
		||||
        ).distinct().values_list('key', flat=True)
 | 
			
		||||
 | 
			
		||||
        granted_node_q = Q()
 | 
			
		||||
        for _key in granted_node_keys:
 | 
			
		||||
            granted_node_q |= Q(nodes__key__startswith=f'{_key}:')
 | 
			
		||||
            granted_node_q |= Q(nodes__key=_key)
 | 
			
		||||
 | 
			
		||||
        q = Q(granted_by_permissions__users=user) | \
 | 
			
		||||
            Q(granted_by_permissions__user_groups__users=user)
 | 
			
		||||
 | 
			
		||||
        if granted_node_q:
 | 
			
		||||
            q |= granted_node_q
 | 
			
		||||
 | 
			
		||||
        return Asset.objects.filter(q).distinct().only(
 | 
			
		||||
            *self.only_fields
 | 
			
		||||
        )
 | 
			
		||||
        queryset = get_user_granted_all_assets(self.user)
 | 
			
		||||
        return queryset.only(*self.only_fields)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyAllAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserAllGrantedAssetsApi):
 | 
			
		||||
| 
						 | 
				
			
			@ -113,33 +110,30 @@ class MyAllAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserAllGrantedAssets
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
@method_decorator(tmp_to_root_org(), name='list')
 | 
			
		||||
class UserGrantedNodeAssetsApi(UserGrantedNodeDispatchMixin, ListAPIView):
 | 
			
		||||
class UserGrantedNodeAssetsApi(UserNodeGrantStatusDispatchMixin, ListAPIView):
 | 
			
		||||
    serializer_class = serializers.AssetGrantedSerializer
 | 
			
		||||
    only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
 | 
			
		||||
    filter_fields = ['hostname', 'ip', 'id', 'comment']
 | 
			
		||||
    search_fields = ['hostname', 'ip', 'comment']
 | 
			
		||||
    pagination_class = GrantedAssetLimitOffsetPagination
 | 
			
		||||
    pagination_node: Node
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        node_id = self.kwargs.get("node_id")
 | 
			
		||||
        user = self.user
 | 
			
		||||
 | 
			
		||||
        mapping_node: UserGrantedMappingNode = get_object_or_none(
 | 
			
		||||
            UserGrantedMappingNode, user=user, node_id=node_id)
 | 
			
		||||
        node = Node.objects.get(id=node_id)
 | 
			
		||||
        return self.dispatch_node_process(node.key, mapping_node, node)
 | 
			
		||||
        self.pagination_node = node
 | 
			
		||||
        return self.dispatch_get_data(node.key, self.user)
 | 
			
		||||
 | 
			
		||||
    def on_granted_node(self, key, mapping_node: UserGrantedMappingNode, node: Node = None):
 | 
			
		||||
        self.node = node
 | 
			
		||||
        return Asset.objects.filter(
 | 
			
		||||
            Q(nodes__key__startswith=f'{node.key}:') |
 | 
			
		||||
            Q(nodes__id=node.id)
 | 
			
		||||
        ).distinct()
 | 
			
		||||
    def get_data_on_node_direct_granted(self, key):
 | 
			
		||||
        # 如果这个节点是直接授权的(或者说祖先节点直接授权的), 获取下面的所有资产
 | 
			
		||||
        return Node.get_node_all_assets_by_key_v2(key)
 | 
			
		||||
 | 
			
		||||
    def on_ungranted_node(self, key, mapping_node: UserGrantedMappingNode, node: Node = None):
 | 
			
		||||
        self.node = mapping_node
 | 
			
		||||
        user = self.user
 | 
			
		||||
        return get_node_all_granted_assets(user, node.key)
 | 
			
		||||
    def get_data_on_node_indirect_granted(self, key):
 | 
			
		||||
        self.pagination_node = self.get_mapping_node_by_key(key)
 | 
			
		||||
        return get_node_all_granted_assets(self.user, key)
 | 
			
		||||
 | 
			
		||||
    def get_data_on_node_not_granted(self, key):
 | 
			
		||||
        return Asset.objects.none()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserGrantedNodeAssetsForAdminApi(ForAdminMixin, UserGrantedNodeAssetsApi):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,25 +1,24 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
from django.db.models import Q, F
 | 
			
		||||
from perms.api.user_permission.mixin import ForAdminMixin, ForUserMixin
 | 
			
		||||
import abc
 | 
			
		||||
from rest_framework.generics import (
 | 
			
		||||
    ListAPIView
 | 
			
		||||
)
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
 | 
			
		||||
from perms.utils.user_node_tree import (
 | 
			
		||||
    node_annotate_mapping_node, get_ungranted_node_children,
 | 
			
		||||
    is_granted, get_granted_assets_amount, node_annotate_set_granted,
 | 
			
		||||
)
 | 
			
		||||
from common.utils.django import get_object_or_none
 | 
			
		||||
from common.utils import lazyproperty
 | 
			
		||||
from perms.models import UserGrantedMappingNode
 | 
			
		||||
from orgs.utils import tmp_to_root_org
 | 
			
		||||
from assets.api.mixin import SerializeToTreeNodeMixin
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
from ...hands import Node
 | 
			
		||||
from .mixin import UserGrantedNodeDispatchMixin
 | 
			
		||||
from .mixin import ForAdminMixin, ForUserMixin, UserNodeGrantStatusDispatchMixin
 | 
			
		||||
from ...hands import Node, User
 | 
			
		||||
from ... import serializers
 | 
			
		||||
from ...utils.user_asset_permission import (
 | 
			
		||||
    get_indirect_granted_node_children,
 | 
			
		||||
    get_user_granted_nodes_list_via_mapping_node,
 | 
			
		||||
    get_top_level_granted_nodes,
 | 
			
		||||
    init_user_tree_if_need,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__name__)
 | 
			
		||||
| 
						 | 
				
			
			@ -32,12 +31,13 @@ __all__ = [
 | 
			
		|||
    'MyGrantedNodeChildrenApi',
 | 
			
		||||
    'UserGrantedNodeChildrenAsTreeForAdminApi',
 | 
			
		||||
    'MyGrantedNodeChildrenAsTreeApi',
 | 
			
		||||
    'NodeChildrenAsTreeApi',
 | 
			
		||||
    'BaseGrantedNodeAsTreeApi',
 | 
			
		||||
    'UserGrantedNodesMixin',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GrantedNodeBaseApi(ListAPIView):
 | 
			
		||||
    @lazyproperty
 | 
			
		||||
class _GrantedNodeStructApi(ListAPIView, metaclass=abc.ABCMeta):
 | 
			
		||||
    @property
 | 
			
		||||
    def user(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,113 +47,105 @@ class GrantedNodeBaseApi(ListAPIView):
 | 
			
		|||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NodeChildrenApi(GrantedNodeBaseApi):
 | 
			
		||||
class NodeChildrenMixin:
 | 
			
		||||
    def get_children(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def get_nodes(self):
 | 
			
		||||
        nodes = self.get_children()
 | 
			
		||||
        return nodes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseGrantedNodeApi(_GrantedNodeStructApi, metaclass=abc.ABCMeta):
 | 
			
		||||
    serializer_class = serializers.NodeGrantedSerializer
 | 
			
		||||
 | 
			
		||||
    @tmp_to_root_org()
 | 
			
		||||
    def list(self, request, *args, **kwargs):
 | 
			
		||||
        init_user_tree_if_need(self.user)
 | 
			
		||||
        nodes = self.get_nodes()
 | 
			
		||||
        serializer = self.get_serializer(nodes, many=True)
 | 
			
		||||
        return Response(serializer.data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, GrantedNodeBaseApi):
 | 
			
		||||
class BaseNodeChildrenApi(NodeChildrenMixin, BaseGrantedNodeApi, metaclass=abc.ABCMeta):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseGrantedNodeAsTreeApi(SerializeToTreeNodeMixin, _GrantedNodeStructApi, metaclass=abc.ABCMeta):
 | 
			
		||||
    @tmp_to_root_org()
 | 
			
		||||
    def list(self, request, *args, **kwargs):
 | 
			
		||||
        init_user_tree_if_need(self.user)
 | 
			
		||||
        nodes = self.get_nodes()
 | 
			
		||||
        nodes = self.serialize_nodes(nodes, with_asset_amount=True)
 | 
			
		||||
        return Response(data=nodes)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserGrantedNodeChildrenMixin(UserGrantedNodeDispatchMixin):
 | 
			
		||||
class BaseNodeChildrenAsTreeApi(NodeChildrenMixin, BaseGrantedNodeAsTreeApi, metaclass=abc.ABCMeta):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
    def get_nodes(self):
 | 
			
		||||
 | 
			
		||||
class UserGrantedNodeChildrenMixin(UserNodeGrantStatusDispatchMixin):
 | 
			
		||||
    user: User
 | 
			
		||||
    request: Request
 | 
			
		||||
 | 
			
		||||
    def get_children(self):
 | 
			
		||||
        user = self.user
 | 
			
		||||
        key = self.request.query_params.get('key')
 | 
			
		||||
 | 
			
		||||
        self.submit_update_mapping_node_task(user)
 | 
			
		||||
 | 
			
		||||
        if not key:
 | 
			
		||||
            nodes = get_ungranted_node_children(user)
 | 
			
		||||
            nodes = list(get_top_level_granted_nodes(user))
 | 
			
		||||
        else:
 | 
			
		||||
            mapping_node = get_object_or_none(
 | 
			
		||||
                UserGrantedMappingNode, user=user, key=key
 | 
			
		||||
            )
 | 
			
		||||
            nodes = self.dispatch_node_process(key, mapping_node, None)
 | 
			
		||||
            nodes = self.dispatch_get_data(key, user)
 | 
			
		||||
        return nodes
 | 
			
		||||
 | 
			
		||||
    def on_granted_node(self, key, mapping_node: UserGrantedMappingNode, node: Node = None):
 | 
			
		||||
    def get_data_on_node_direct_granted(self, key):
 | 
			
		||||
        return Node.objects.filter(parent_key=key)
 | 
			
		||||
 | 
			
		||||
    def on_ungranted_node(self, key, mapping_node: UserGrantedMappingNode, node: Node = None):
 | 
			
		||||
        user = self.user
 | 
			
		||||
        nodes = get_ungranted_node_children(user, key)
 | 
			
		||||
    def get_data_on_node_indirect_granted(self, key):
 | 
			
		||||
        nodes = get_indirect_granted_node_children(self.user, key)
 | 
			
		||||
        return nodes
 | 
			
		||||
 | 
			
		||||
    def get_data_on_node_not_granted(self, key):
 | 
			
		||||
        return Node.objects.none()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserGrantedNodesMixin:
 | 
			
		||||
    """
 | 
			
		||||
    查询用户授权的所有节点 直接授权节点 + 授权资产关联的节点
 | 
			
		||||
    """
 | 
			
		||||
    user: User
 | 
			
		||||
 | 
			
		||||
    def get_nodes(self):
 | 
			
		||||
        user = self.user
 | 
			
		||||
 | 
			
		||||
        # 获取 `UserGrantedMappingNode` 中对应的 `Node`
 | 
			
		||||
        nodes = Node.objects.filter(
 | 
			
		||||
            mapping_nodes__user=user,
 | 
			
		||||
        ).annotate(**node_annotate_mapping_node).distinct()
 | 
			
		||||
 | 
			
		||||
        key2nodes_mapper = {}
 | 
			
		||||
        descendant_q = Q()
 | 
			
		||||
 | 
			
		||||
        for _node in nodes:
 | 
			
		||||
            if not is_granted(_node):
 | 
			
		||||
                # 未授权的节点资产数量设置为 `UserGrantedMappingNode` 中的数量
 | 
			
		||||
                _node.assets_amount = get_granted_assets_amount(_node)
 | 
			
		||||
            else:
 | 
			
		||||
                # 直接授权的节点
 | 
			
		||||
                # 增加查询后代节点的过滤条件
 | 
			
		||||
                descendant_q |= Q(key__startswith=f'{_node.key}:')
 | 
			
		||||
 | 
			
		||||
            key2nodes_mapper[_node.key] = _node
 | 
			
		||||
 | 
			
		||||
        if descendant_q:
 | 
			
		||||
            descendant_nodes = Node.objects.filter(descendant_q).annotate(**node_annotate_set_granted)
 | 
			
		||||
            for _node in descendant_nodes:
 | 
			
		||||
                key2nodes_mapper[_node.key] = _node
 | 
			
		||||
 | 
			
		||||
        all_nodes = key2nodes_mapper.values()
 | 
			
		||||
        return all_nodes
 | 
			
		||||
        return get_user_granted_nodes_list_via_mapping_node(self.user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------
 | 
			
		||||
# 最终的 api
 | 
			
		||||
class UserGrantedNodeChildrenForAdminApi(ForAdminMixin, UserGrantedNodeChildrenMixin, NodeChildrenApi):
 | 
			
		||||
class UserGrantedNodeChildrenForAdminApi(ForAdminMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenApi):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyGrantedNodeChildrenApi(ForUserMixin, UserGrantedNodeChildrenMixin, NodeChildrenApi):
 | 
			
		||||
class MyGrantedNodeChildrenApi(ForUserMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenApi):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserGrantedNodeChildrenAsTreeForAdminApi(ForAdminMixin, UserGrantedNodeChildrenMixin, NodeChildrenAsTreeApi):
 | 
			
		||||
class UserGrantedNodeChildrenAsTreeForAdminApi(ForAdminMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenAsTreeApi):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyGrantedNodeChildrenAsTreeApi(ForUserMixin, UserGrantedNodeChildrenMixin, NodeChildrenAsTreeApi):
 | 
			
		||||
class MyGrantedNodeChildrenAsTreeApi(ForUserMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenAsTreeApi):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserGrantedNodesForAdminApi(ForAdminMixin, UserGrantedNodesMixin, NodeChildrenApi):
 | 
			
		||||
class UserGrantedNodesForAdminApi(ForAdminMixin, UserGrantedNodesMixin, BaseGrantedNodeApi):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyGrantedNodesApi(ForUserMixin, UserGrantedNodesMixin, NodeChildrenApi):
 | 
			
		||||
class MyGrantedNodesApi(ForUserMixin, UserGrantedNodesMixin, BaseGrantedNodeApi):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyGrantedNodesAsTreeApi(ForUserMixin, UserGrantedNodesMixin, NodeChildrenAsTreeApi):
 | 
			
		||||
class MyGrantedNodesAsTreeApi(ForUserMixin, UserGrantedNodesMixin, BaseGrantedNodeAsTreeApi):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,80 +3,44 @@
 | 
			
		|||
from rest_framework.generics import ListAPIView
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from django.db.models import Q, F
 | 
			
		||||
 | 
			
		||||
from users.models import User
 | 
			
		||||
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
 | 
			
		||||
from common.utils.django import get_object_or_none
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
from .user_permission_nodes import MyGrantedNodesAsTreeApi
 | 
			
		||||
from .mixin import UserGrantedNodeDispatchMixin
 | 
			
		||||
from perms.models import UserGrantedMappingNode
 | 
			
		||||
from perms.utils.user_node_tree import (
 | 
			
		||||
    TMP_GRANTED_FIELD, TMP_GRANTED_ASSETS_AMOUNT_FIELD, node_annotate_mapping_node,
 | 
			
		||||
    is_granted, get_granted_assets_amount, node_annotate_set_granted,
 | 
			
		||||
    get_granted_q, get_ungranted_node_children
 | 
			
		||||
from common.permissions import IsValidUser
 | 
			
		||||
from common.utils import get_logger, get_object_or_none
 | 
			
		||||
from .mixin import UserNodeGrantStatusDispatchMixin, ForUserMixin, ForAdminMixin
 | 
			
		||||
from ...utils.user_asset_permission import (
 | 
			
		||||
    get_user_resources_q_granted_by_permissions,
 | 
			
		||||
    get_indirect_granted_node_children, UNGROUPED_NODE_KEY, FAVORITE_NODE_KEY,
 | 
			
		||||
    get_user_direct_granted_assets, get_top_level_granted_nodes,
 | 
			
		||||
    get_user_granted_nodes_list_via_mapping_node,
 | 
			
		||||
    get_user_granted_all_assets, init_user_tree_if_need,
 | 
			
		||||
    get_user_all_assetpermission_ids,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from assets.models import Asset
 | 
			
		||||
from assets.models import Asset, FavoriteAsset
 | 
			
		||||
from assets.api import SerializeToTreeNodeMixin
 | 
			
		||||
from orgs.utils import tmp_to_root_org
 | 
			
		||||
from ...hands import Node
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__name__)
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    'MyGrantedNodesAsTreeApi',
 | 
			
		||||
    'UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi',
 | 
			
		||||
    'MyGrantedNodesWithAssetsAsTreeApi',
 | 
			
		||||
    'MyGrantedNodeChildrenWithAssetsAsTreeApi',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyGrantedNodesWithAssetsAsTreeApi(SerializeToTreeNodeMixin, ListAPIView):
 | 
			
		||||
    permission_classes = (IsValidUser,)
 | 
			
		||||
 | 
			
		||||
    @tmp_to_root_org()
 | 
			
		||||
    def list(self, request: Request, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        此算法依赖 UserGrantedMappingNode
 | 
			
		||||
        获取所有授权的节点和资产
 | 
			
		||||
 | 
			
		||||
        Node = UserGrantedMappingNode + 授权节点的子节点
 | 
			
		||||
        Asset = 授权节点的资产 + 直接授权的资产
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        user = request.user
 | 
			
		||||
 | 
			
		||||
        # 获取 `UserGrantedMappingNode` 中对应的 `Node`
 | 
			
		||||
        nodes = Node.objects.filter(
 | 
			
		||||
            mapping_nodes__user=user,
 | 
			
		||||
        ).annotate(**node_annotate_mapping_node).distinct()
 | 
			
		||||
 | 
			
		||||
        key2nodes_mapper = {}
 | 
			
		||||
        descendant_q = Q()
 | 
			
		||||
        granted_q = Q()
 | 
			
		||||
 | 
			
		||||
        for _node in nodes:
 | 
			
		||||
            if not is_granted(_node):
 | 
			
		||||
                # 未授权的节点资产数量设置为 `UserGrantedMappingNode` 中的数量
 | 
			
		||||
                _node.assets_amount = get_granted_assets_amount(_node)
 | 
			
		||||
            else:
 | 
			
		||||
                # 直接授权的节点
 | 
			
		||||
 | 
			
		||||
                # 增加查询该节点及其后代节点资产的过滤条件
 | 
			
		||||
                granted_q |= Q(nodes__key__startswith=f'{_node.key}:')
 | 
			
		||||
                granted_q |= Q(nodes__key=_node.key)
 | 
			
		||||
 | 
			
		||||
                # 增加查询后代节点的过滤条件
 | 
			
		||||
                descendant_q |= Q(key__startswith=f'{_node.key}:')
 | 
			
		||||
 | 
			
		||||
            key2nodes_mapper[_node.key] = _node
 | 
			
		||||
 | 
			
		||||
        if descendant_q:
 | 
			
		||||
            descendant_nodes = Node.objects.filter(descendant_q).annotate(**node_annotate_set_granted)
 | 
			
		||||
            for _node in descendant_nodes:
 | 
			
		||||
                key2nodes_mapper[_node.key] = _node
 | 
			
		||||
 | 
			
		||||
        all_nodes = key2nodes_mapper.values()
 | 
			
		||||
 | 
			
		||||
        # 查询出所有资产
 | 
			
		||||
        all_assets = Asset.objects.filter(
 | 
			
		||||
            get_granted_q(user) |
 | 
			
		||||
            granted_q
 | 
			
		||||
        ).annotate(parent_key=F('nodes__key')).distinct()
 | 
			
		||||
        init_user_tree_if_need(user)
 | 
			
		||||
        all_nodes = get_user_granted_nodes_list_via_mapping_node(user)
 | 
			
		||||
        all_assets = get_user_granted_all_assets(user)
 | 
			
		||||
 | 
			
		||||
        data = [
 | 
			
		||||
            *self.serialize_nodes(all_nodes, with_asset_amount=True),
 | 
			
		||||
| 
						 | 
				
			
			@ -85,61 +49,70 @@ class MyGrantedNodesWithAssetsAsTreeApi(SerializeToTreeNodeMixin, ListAPIView):
 | 
			
		|||
        return Response(data=data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi(UserGrantedNodeDispatchMixin, SerializeToTreeNodeMixin, ListAPIView):
 | 
			
		||||
    permission_classes = (IsOrgAdminOrAppUser, )
 | 
			
		||||
class UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi(ForAdminMixin, UserNodeGrantStatusDispatchMixin,
 | 
			
		||||
                                                         SerializeToTreeNodeMixin, ListAPIView):
 | 
			
		||||
    """
 | 
			
		||||
    带资产的授权树
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def on_granted_node(self, key, mapping_node: UserGrantedMappingNode, node: Node = None):
 | 
			
		||||
    def get_data_on_node_direct_granted(self, key):
 | 
			
		||||
        nodes = Node.objects.filter(parent_key=key)
 | 
			
		||||
        assets = Asset.objects.filter(nodes__key=key).distinct()
 | 
			
		||||
        assets = Asset.org_objects.filter(nodes__key=key).distinct()
 | 
			
		||||
        assets = assets.prefetch_related('platform')
 | 
			
		||||
        return nodes, assets
 | 
			
		||||
 | 
			
		||||
    def on_ungranted_node(self, key, mapping_node: UserGrantedMappingNode, node: Node = None):
 | 
			
		||||
        user = self.get_user()
 | 
			
		||||
        assets = Asset.objects.none()
 | 
			
		||||
        nodes = Node.objects.filter(
 | 
			
		||||
            parent_key=key,
 | 
			
		||||
            mapping_nodes__user=user,
 | 
			
		||||
        ).annotate(
 | 
			
		||||
            **node_annotate_mapping_node
 | 
			
		||||
    def get_data_on_node_indirect_granted(self, key):
 | 
			
		||||
        user = self.user
 | 
			
		||||
        asset_perm_ids = get_user_all_assetpermission_ids(user)
 | 
			
		||||
 | 
			
		||||
        nodes = get_indirect_granted_node_children(user, key)
 | 
			
		||||
 | 
			
		||||
        assets = Asset.org_objects.filter(
 | 
			
		||||
            nodes__key=key,
 | 
			
		||||
        ).filter(
 | 
			
		||||
            granted_by_permissions__id__in=asset_perm_ids
 | 
			
		||||
        ).distinct()
 | 
			
		||||
 | 
			
		||||
        # TODO 可配置
 | 
			
		||||
        for _node in nodes:
 | 
			
		||||
            if not is_granted(_node):
 | 
			
		||||
                _node.assets_amount = get_granted_assets_amount(_node)
 | 
			
		||||
 | 
			
		||||
        if mapping_node.asset_granted:
 | 
			
		||||
            assets = Asset.objects.filter(
 | 
			
		||||
                nodes__key=key,
 | 
			
		||||
            ).filter(get_granted_q(user))
 | 
			
		||||
        assets = assets.prefetch_related('platform')
 | 
			
		||||
        return nodes, assets
 | 
			
		||||
 | 
			
		||||
    def get_user(self):
 | 
			
		||||
        user_id = self.kwargs.get('pk')
 | 
			
		||||
        return User.objects.get(id=user_id)
 | 
			
		||||
    def get_data_on_node_not_granted(self, key):
 | 
			
		||||
        return Node.objects.none(), Asset.objects.none()
 | 
			
		||||
 | 
			
		||||
    def get_data(self, key, user):
 | 
			
		||||
        assets, nodes = [], []
 | 
			
		||||
        if not key:
 | 
			
		||||
            root_nodes = get_top_level_granted_nodes(user)
 | 
			
		||||
            nodes.extend(root_nodes)
 | 
			
		||||
        elif key == UNGROUPED_NODE_KEY:
 | 
			
		||||
            assets = get_user_direct_granted_assets(user)
 | 
			
		||||
            assets = assets.prefetch_related('platform')
 | 
			
		||||
        elif key == FAVORITE_NODE_KEY:
 | 
			
		||||
            assets = FavoriteAsset.get_user_favorite_assets(user)
 | 
			
		||||
        else:
 | 
			
		||||
            nodes, assets = self.dispatch_get_data(key, user)
 | 
			
		||||
        return nodes, assets
 | 
			
		||||
 | 
			
		||||
    def id2key_if_have(self):
 | 
			
		||||
        id = self.request.query_params.get('id')
 | 
			
		||||
        if id is not None:
 | 
			
		||||
            node = get_object_or_none(Node, id=id)
 | 
			
		||||
            if node:
 | 
			
		||||
                return node.key
 | 
			
		||||
 | 
			
		||||
    @tmp_to_root_org()
 | 
			
		||||
    def list(self, request: Request, *args, **kwargs):
 | 
			
		||||
        user = self.get_user()
 | 
			
		||||
        key = request.query_params.get('key')
 | 
			
		||||
        self.submit_update_mapping_node_task(user)
 | 
			
		||||
        key = self.request.query_params.get('key')
 | 
			
		||||
        if key is None:
 | 
			
		||||
            key = self.id2key_if_have()
 | 
			
		||||
 | 
			
		||||
        nodes = []
 | 
			
		||||
        assets = []
 | 
			
		||||
        if not key:
 | 
			
		||||
            root_nodes = get_ungranted_node_children(user)
 | 
			
		||||
            nodes.extend(root_nodes)
 | 
			
		||||
        else:
 | 
			
		||||
            mapping_node: UserGrantedMappingNode = get_object_or_none(
 | 
			
		||||
                UserGrantedMappingNode, user=user, key=key)
 | 
			
		||||
            nodes, assets = self.dispatch_node_process(key, mapping_node)
 | 
			
		||||
        nodes = self.serialize_nodes(nodes, with_asset_amount=True)
 | 
			
		||||
        assets = self.serialize_assets(assets, key)
 | 
			
		||||
        return Response(data=[*nodes, *assets])
 | 
			
		||||
        user = self.user
 | 
			
		||||
        init_user_tree_if_need(user)
 | 
			
		||||
        nodes, assets = self.get_data(key, user)
 | 
			
		||||
 | 
			
		||||
        tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True)
 | 
			
		||||
        tree_assets = self.serialize_assets(assets, key)
 | 
			
		||||
        return Response(data=[*tree_nodes, *tree_assets])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyGrantedNodeChildrenWithAssetsAsTreeApi(UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi):
 | 
			
		||||
    permission_classes = (IsValidUser, )
 | 
			
		||||
 | 
			
		||||
    def get_user(self):
 | 
			
		||||
        return self.request.user
 | 
			
		||||
class MyGrantedNodeChildrenWithAssetsAsTreeApi(ForUserMixin, UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi):
 | 
			
		||||
    pass
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ from functools import reduce
 | 
			
		|||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from common.db import models
 | 
			
		||||
from common.fields.model import JsonListTextField
 | 
			
		||||
from common.utils import lazyproperty
 | 
			
		||||
from orgs.models import Organization
 | 
			
		||||
from orgs.utils import get_current_org
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +16,8 @@ from .base import BasePermission
 | 
			
		|||
__all__ = [
 | 
			
		||||
    'AssetPermission', 'Action', 'UserGrantedMappingNode', 'RebuildUserTreeTask',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# 使用场景
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +99,14 @@ class AssetPermission(BasePermission):
 | 
			
		|||
        verbose_name = _("Asset permission")
 | 
			
		||||
        ordering = ('name',)
 | 
			
		||||
 | 
			
		||||
    @lazyproperty
 | 
			
		||||
    def users_amount(self):
 | 
			
		||||
        return self.users.count()
 | 
			
		||||
 | 
			
		||||
    @lazyproperty
 | 
			
		||||
    def user_groups_amount(self):
 | 
			
		||||
        return self.user_groups.count()
 | 
			
		||||
 | 
			
		||||
    @lazyproperty
 | 
			
		||||
    def assets_amount(self):
 | 
			
		||||
        return self.assets.count()
 | 
			
		||||
| 
						 | 
				
			
			@ -186,6 +195,22 @@ class UserGrantedMappingNode(FamilyMixin, models.JMSBaseModel):
 | 
			
		|||
    parent_key = models.CharField(max_length=64, default='', verbose_name=_('Parent key'), db_index=True)  # '1:1:1:1'
 | 
			
		||||
    assets_amount = models.IntegerField(default=0)
 | 
			
		||||
 | 
			
		||||
    GRANTED_DIRECT = 1
 | 
			
		||||
    GRANTED_INDIRECT = 2
 | 
			
		||||
    GRANTED_NONE = 0
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_node_granted_status(cls, key, user):
 | 
			
		||||
        ancestor_keys = Node.get_node_ancestor_keys(key, with_self=True)
 | 
			
		||||
        has_granted = UserGrantedMappingNode.objects.filter(
 | 
			
		||||
            key__in=ancestor_keys, user=user
 | 
			
		||||
        ).values_list('granted', flat=True)
 | 
			
		||||
        if not has_granted:
 | 
			
		||||
            return cls.GRANTED_NONE
 | 
			
		||||
        if any(list(has_granted)):
 | 
			
		||||
            return cls.GRANTED_DIRECT
 | 
			
		||||
        return cls.GRANTED_INDIRECT
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RebuildUserTreeTask(models.JMSBaseModel):
 | 
			
		||||
    user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User'))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@ from orgs.mixins.models import OrgManager
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    'BasePermission',
 | 
			
		||||
    'BasePermission', 'BasePermissionQuerySet'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -46,8 +46,8 @@ class BasePermissionManager(OrgManager):
 | 
			
		|||
class BasePermission(OrgModelMixin):
 | 
			
		||||
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
 | 
			
		||||
    name = models.CharField(max_length=128, verbose_name=_('Name'))
 | 
			
		||||
    users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"))
 | 
			
		||||
    user_groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User group"))
 | 
			
		||||
    users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"), related_name='%(class)ss')
 | 
			
		||||
    user_groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User group"), related_name='%(class)ss')
 | 
			
		||||
    is_active = models.BooleanField(default=True, verbose_name=_('Active'))
 | 
			
		||||
    date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
 | 
			
		||||
    date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired'))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,10 @@
 | 
			
		|||
from rest_framework.pagination import LimitOffsetPagination
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GrantedAssetLimitOffsetPagination(LimitOffsetPagination):
 | 
			
		||||
    def get_count(self, queryset):
 | 
			
		||||
| 
						 | 
				
			
			@ -10,15 +14,15 @@ class GrantedAssetLimitOffsetPagination(LimitOffsetPagination):
 | 
			
		|||
            'key', 'all', 'show_current_asset',
 | 
			
		||||
            'cache_policy', 'display', 'draw'
 | 
			
		||||
        }
 | 
			
		||||
        has_filter = False
 | 
			
		||||
        for k, v in self._request.query_params.items():
 | 
			
		||||
            if k not in exclude_query_params and v is not None:
 | 
			
		||||
                has_filter = True
 | 
			
		||||
                break
 | 
			
		||||
        if has_filter:
 | 
			
		||||
                return super().get_count(queryset)
 | 
			
		||||
        node = getattr(self._view, 'pagination_node', None)
 | 
			
		||||
        if node:
 | 
			
		||||
            logger.debug(f'{self._request.get_full_path()} hit node.assets_amount[{node.assets_amount}]')
 | 
			
		||||
            return node.assets_amount
 | 
			
		||||
        else:
 | 
			
		||||
            return super().get_count(queryset)
 | 
			
		||||
        node = self._view.node
 | 
			
		||||
        return node.assets_amount
 | 
			
		||||
 | 
			
		||||
    def paginate_queryset(self, queryset, request: Request, view=None):
 | 
			
		||||
        self._request = request
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,9 +56,5 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
 | 
			
		|||
    @classmethod
 | 
			
		||||
    def setup_eager_loading(cls, queryset):
 | 
			
		||||
        """ Perform necessary eager loading of data. """
 | 
			
		||||
        queryset = queryset.annotate(
 | 
			
		||||
            users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True),
 | 
			
		||||
            assets_amount=Count('assets', distinct=True), nodes_amount=Count('nodes', distinct=True),
 | 
			
		||||
            system_users_amount=Count('system_users', distinct=True)
 | 
			
		||||
        )
 | 
			
		||||
        queryset = queryset.prefetch_related('users', 'user_groups', 'assets', 'nodes', 'system_users')
 | 
			
		||||
        return queryset
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,13 +2,13 @@
 | 
			
		|||
#
 | 
			
		||||
from itertools import chain
 | 
			
		||||
 | 
			
		||||
from django.db.models.signals import m2m_changed, pre_delete
 | 
			
		||||
from django.db.models.signals import m2m_changed, pre_delete, pre_save
 | 
			
		||||
from django.dispatch import receiver
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
 | 
			
		||||
from perms.tasks import dispatch_mapping_node_tasks
 | 
			
		||||
from users.models import User
 | 
			
		||||
from users.models import User, UserGroup
 | 
			
		||||
from assets.models import Asset
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
from common.exceptions import M2MReverseNotAllowed
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +19,11 @@ from .models import AssetPermission, RemoteAppPermission, RebuildUserTreeTask
 | 
			
		|||
logger = get_logger(__file__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Todo: 检查授权规则到期,从而修改授权规则
 | 
			
		||||
@receiver([pre_save], sender=AssetPermission)
 | 
			
		||||
def on_asset_perm_deactive(instance: AssetPermission, **kwargs):
 | 
			
		||||
    old = AssetPermission.objects.only('is_active').get(id=instance.id)
 | 
			
		||||
    if instance.is_active != old.is_active:
 | 
			
		||||
        create_rebuild_user_tree_task_by_asset_perm(instance)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver([pre_delete], sender=AssetPermission)
 | 
			
		||||
| 
						 | 
				
			
			@ -32,16 +36,17 @@ def create_rebuild_user_tree_task(user_ids):
 | 
			
		|||
    RebuildUserTreeTask.objects.bulk_create(
 | 
			
		||||
        [RebuildUserTreeTask(user_id=i) for i in user_ids]
 | 
			
		||||
    )
 | 
			
		||||
    transaction.on_commit(dispatch_mapping_node_tasks)
 | 
			
		||||
    transaction.on_commit(dispatch_mapping_node_tasks.delay)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_rebuild_user_tree_task_by_asset_perm(asset_perm: AssetPermission):
 | 
			
		||||
    user_ap_query_name = AssetPermission.users.field.related_query_name()
 | 
			
		||||
    group_ap_query_name = AssetPermission.user_groups.field.related_query_name()
 | 
			
		||||
 | 
			
		||||
    user_ap_q = Q(**{f'{user_ap_query_name}': asset_perm})
 | 
			
		||||
    group_ap_q = Q(**{f'groups__{group_ap_query_name}': asset_perm})
 | 
			
		||||
    user_ids = User.objects.filter(user_ap_q | group_ap_q).distinct().values_list('id', flat=True)
 | 
			
		||||
    user_ids = set()
 | 
			
		||||
    user_ids.update(
 | 
			
		||||
        UserGroup.objects.filter(assetpermissions=asset_perm).distinct().values_list('users__id', flat=True)
 | 
			
		||||
    )
 | 
			
		||||
    user_ids.update(
 | 
			
		||||
        User.objects.filter(assetpermissions=asset_perm).distinct().values_list('id', flat=True)
 | 
			
		||||
    )
 | 
			
		||||
    create_rebuild_user_tree_task(user_ids)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,18 +1,22 @@
 | 
			
		|||
# ~*~ coding: utf-8 ~*~
 | 
			
		||||
from __future__ import absolute_import, unicode_literals
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from celery import shared_task
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
from common.utils.timezone import now
 | 
			
		||||
from users.models import User
 | 
			
		||||
from perms.models import RebuildUserTreeTask
 | 
			
		||||
from perms.utils.user_node_tree import rebuild_user_mapping_nodes_if_need_with_lock
 | 
			
		||||
from perms.models import RebuildUserTreeTask, AssetPermission
 | 
			
		||||
from perms.utils.user_asset_permission import rebuild_user_mapping_nodes_if_need_with_lock
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__file__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task(queue='node_tree')
 | 
			
		||||
def rebuild_user_mapping_nodes_celery_task(user_id):
 | 
			
		||||
    logger.info(f'rebuild user[{user_id}] mapping nodes')
 | 
			
		||||
    logger.info(f'>>> rebuild user[{user_id}] mapping nodes')
 | 
			
		||||
    user = User.objects.get(id=user_id)
 | 
			
		||||
    rebuild_user_mapping_nodes_if_need_with_lock(user)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,5 +25,33 @@ def rebuild_user_mapping_nodes_celery_task(user_id):
 | 
			
		|||
def dispatch_mapping_node_tasks():
 | 
			
		||||
    user_ids = RebuildUserTreeTask.objects.all().values_list('user_id', flat=True).distinct()
 | 
			
		||||
    for id in user_ids:
 | 
			
		||||
        logger.info(f'dispatch mapping node task for user[{id}]')
 | 
			
		||||
        logger.info(f'>>> dispatch mapping node task for user[{id}]')
 | 
			
		||||
        rebuild_user_mapping_nodes_celery_task.delay(id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task(queue='check_asset_perm_expired')
 | 
			
		||||
def check_asset_permission_expired():
 | 
			
		||||
    """
 | 
			
		||||
    这里的任务要足够短,不要影响周期任务
 | 
			
		||||
    """
 | 
			
		||||
    periodic = settings.PERM_EXPIRED_CHECK_PERIODIC
 | 
			
		||||
    end = now()
 | 
			
		||||
    start = end - timedelta(seconds=periodic * 1.2)
 | 
			
		||||
    ids = AssetPermission.objects.filter(
 | 
			
		||||
        date_expired__gt=start, date_expired__lt=end
 | 
			
		||||
    ).distinct().values_list('id', flat=True)
 | 
			
		||||
    logger.info(f'>>> checking {start} to {end} have {ids} expired')
 | 
			
		||||
    dispatch_process_expired_asset_permission.delay(ids)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task(queue='node_tree')
 | 
			
		||||
def dispatch_process_expired_asset_permission(asset_perm_ids):
 | 
			
		||||
    user_ids = User.objects.filter(
 | 
			
		||||
        Q(assetpermissions__id__in=asset_perm_ids) |
 | 
			
		||||
        Q(groups__assetpermissions__id__in=asset_perm_ids)
 | 
			
		||||
    ).distinct().values_list('id', flat=True)
 | 
			
		||||
    RebuildUserTreeTask.objects.bulk_create(
 | 
			
		||||
        [RebuildUserTreeTask(user_id=user_id) for user_id in user_ids]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    dispatch_mapping_node_tasks.delay()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,14 +57,22 @@ user_permission_urlpatterns = [
 | 
			
		|||
    # 普通用户 -> 命令执行 -> 左侧树
 | 
			
		||||
    path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
 | 
			
		||||
 | 
			
		||||
    # Node children with assets as tree
 | 
			
		||||
    # 主要用于 luna 页面,带资产的节点树
 | 
			
		||||
    path('<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
 | 
			
		||||
    path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
 | 
			
		||||
 | 
			
		||||
    # Node assets
 | 
			
		||||
    # 查询授权树上某个节点的所有资产
 | 
			
		||||
    path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsForAdminApi.as_view(), name='user-node-assets'),
 | 
			
		||||
    path('nodes/<uuid:node_id>/assets/', api.MyGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
 | 
			
		||||
 | 
			
		||||
    # 未分组的资产
 | 
			
		||||
    path('<uuid:pk>/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsForAdminApi.as_view(), name='user-ungrouped-assets'),
 | 
			
		||||
    path('nodes/ungrouped/assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-ungrouped-assets'),
 | 
			
		||||
 | 
			
		||||
    # 收藏的资产
 | 
			
		||||
    path('<uuid:pk>/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsForAdminApi.as_view(), name='user-ungrouped-assets'),
 | 
			
		||||
    path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'),
 | 
			
		||||
 | 
			
		||||
    # Asset System users
 | 
			
		||||
    path('<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersForAdminApi.as_view(), name='user-asset-system-users'),
 | 
			
		||||
    path('assets/<uuid:asset_id>/system-users/', api.MyGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,4 +5,4 @@ from .asset_permission import *
 | 
			
		|||
from .remote_app_permission import *
 | 
			
		||||
from .database_app_permission import *
 | 
			
		||||
from .k8s_app_permission import *
 | 
			
		||||
from .user_node_tree import *
 | 
			
		||||
from .user_asset_permission import *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,47 +4,12 @@ from django.db.models import Q
 | 
			
		|||
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
from ..models import AssetPermission
 | 
			
		||||
from ..hands import Asset, User
 | 
			
		||||
from users.models import UserGroup
 | 
			
		||||
from ..hands import Asset, User, UserGroup
 | 
			
		||||
from perms.models.base import BasePermissionQuerySet
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__file__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_user_permissions(user, include_group=True):
 | 
			
		||||
    if include_group:
 | 
			
		||||
        groups = user.groups.all()
 | 
			
		||||
        arg = Q(users=user) | Q(user_groups__in=groups)
 | 
			
		||||
    else:
 | 
			
		||||
        arg = Q(users=user)
 | 
			
		||||
    return AssetPermission.get_queryset_with_prefetch().filter(arg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_user_group_permissions(user_group):
 | 
			
		||||
    return AssetPermission.get_queryset_with_prefetch().filter(
 | 
			
		||||
        user_groups=user_group
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_asset_permissions(asset, include_node=True):
 | 
			
		||||
    if include_node:
 | 
			
		||||
        nodes = asset.get_all_nodes(flat=True)
 | 
			
		||||
        arg = Q(assets=asset) | Q(nodes__in=nodes)
 | 
			
		||||
    else:
 | 
			
		||||
        arg = Q(assets=asset)
 | 
			
		||||
    return AssetPermission.objects.valid().filter(arg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_node_permissions(node):
 | 
			
		||||
    return AssetPermission.objects.valid().filter(nodes=node)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_system_user_permissions(system_user):
 | 
			
		||||
    return AssetPermission.objects.valid().filter(
 | 
			
		||||
        system_users=system_user
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_asset_system_users_id_with_actions(asset_perm_queryset: BasePermissionQuerySet, asset: Asset):
 | 
			
		||||
    nodes = asset.get_nodes()
 | 
			
		||||
    node_keys = set()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,15 +6,16 @@ import inspect
 | 
			
		|||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.db.models import F, Q, Value, BooleanField
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
 | 
			
		||||
from common.utils import get_logger
 | 
			
		||||
from common.const.distributed_lock_key import UPDATE_MAPPING_NODE_TASK_LOCK_KEY
 | 
			
		||||
from orgs.utils import tmp_to_root_org
 | 
			
		||||
from common.utils.timezone import dt_formater, now
 | 
			
		||||
from assets.models import Node, Asset
 | 
			
		||||
from assets.models import Node, Asset, FavoriteAsset
 | 
			
		||||
from django.db.transaction import atomic
 | 
			
		||||
from orgs import lock
 | 
			
		||||
from perms.models import UserGrantedMappingNode, RebuildUserTreeTask
 | 
			
		||||
from perms.models import UserGrantedMappingNode, RebuildUserTreeTask, AssetPermission
 | 
			
		||||
from users.models import User
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__name__)
 | 
			
		||||
| 
						 | 
				
			
			@ -22,24 +23,35 @@ logger = get_logger(__name__)
 | 
			
		|||
ADD = 'add'
 | 
			
		||||
REMOVE = 'remove'
 | 
			
		||||
 | 
			
		||||
UNGROUPED_NODE_KEY = 'ungrouped'
 | 
			
		||||
FAVORITE_NODE_KEY = 'favorite'
 | 
			
		||||
 | 
			
		||||
TMP_GRANTED_FIELD = '_granted'
 | 
			
		||||
TMP_ASSET_GRANTED_FIELD = '_asset_granted'
 | 
			
		||||
TMP_GRANTED_ASSETS_AMOUNT_FIELD = '_granted_assets_amount'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 使用场景
 | 
			
		||||
# Asset.objects.filter(get_granted_q(user))
 | 
			
		||||
def get_granted_q(user: User):
 | 
			
		||||
# Asset.objects.filter(get_user_resources_q_granted_by_permissions(user))
 | 
			
		||||
def get_user_resources_q_granted_by_permissions(user: User):
 | 
			
		||||
    """
 | 
			
		||||
    获取用户关联的 asset permission 或者 用户组关联的 asset permission 获取规则,
 | 
			
		||||
    前提 AssetPermission 对象中的 related_name 为 granted_by_permissions
 | 
			
		||||
    :param user:
 | 
			
		||||
    :return:
 | 
			
		||||
    """
 | 
			
		||||
    _now = now()
 | 
			
		||||
    return reduce(and_, (
 | 
			
		||||
        Q(granted_by_permissions__date_start__lt=_now),
 | 
			
		||||
        Q(granted_by_permissions__date_expired__gt=_now),
 | 
			
		||||
        Q(granted_by_permissions__is_active=True),
 | 
			
		||||
        (Q(granted_by_permissions__users=user) | Q(granted_by_permissions__user_groups__users=user))
 | 
			
		||||
        (
 | 
			
		||||
            Q(granted_by_permissions__users=user) |
 | 
			
		||||
            Q(granted_by_permissions__user_groups__users=user)
 | 
			
		||||
        )
 | 
			
		||||
    ))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TMP_GRANTED_FIELD = '_granted'
 | 
			
		||||
TMP_ASSET_GRANTED_FIELD = '_asset_granted'
 | 
			
		||||
TMP_GRANTED_ASSETS_AMOUNT_FIELD = '_granted_assets_amount'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 使用场景
 | 
			
		||||
# `Node.objects.annotate(**node_annotate_mapping_node)`
 | 
			
		||||
node_annotate_mapping_node = {
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +68,7 @@ node_annotate_set_granted = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_granted(node):
 | 
			
		||||
def is_direct_granted_by_annotate(node):
 | 
			
		||||
    return getattr(node, TMP_GRANTED_FIELD, False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +151,7 @@ def compute_tmp_mapping_node_from_perm(user: User):
 | 
			
		|||
 | 
			
		||||
    # 查询直接授权节点
 | 
			
		||||
    nodes = Node.objects.filter(
 | 
			
		||||
        get_granted_q(user)
 | 
			
		||||
        get_user_resources_q_granted_by_permissions(user)
 | 
			
		||||
    ).distinct().only(*node_only_fields)
 | 
			
		||||
    granted_key_set = {_node.key for _node in nodes}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +177,7 @@ def compute_tmp_mapping_node_from_perm(user: User):
 | 
			
		|||
    def process_direct_granted_assets():
 | 
			
		||||
        # 查询直接授权资产
 | 
			
		||||
        asset_ids = Asset.objects.filter(
 | 
			
		||||
            get_granted_q(user)
 | 
			
		||||
            get_user_resources_q_granted_by_permissions(user)
 | 
			
		||||
        ).distinct().values_list('id', flat=True)
 | 
			
		||||
        # 查询授权资产关联的节点设置
 | 
			
		||||
        granted_asset_nodes = Node.objects.filter(
 | 
			
		||||
| 
						 | 
				
			
			@ -227,7 +239,10 @@ def set_node_granted_assets_amount(user, node):
 | 
			
		|||
    if _granted:
 | 
			
		||||
        assets_amount = node.assets_amount
 | 
			
		||||
    else:
 | 
			
		||||
        assets_amount = count_node_all_granted_assets(user, node.key)
 | 
			
		||||
        if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
 | 
			
		||||
            assets_amount = count_direct_granted_node_assets(user, node.key)
 | 
			
		||||
        else:
 | 
			
		||||
            assets_amount = count_node_all_granted_assets(user, node.key)
 | 
			
		||||
    setattr(node, TMP_GRANTED_ASSETS_AMOUNT_FIELD, assets_amount)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -238,6 +253,68 @@ def rebuild_user_mapping_nodes(user):
 | 
			
		|||
    create_mapping_nodes(user, tmp_nodes)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_user_granted_nodes_list_via_mapping_node(user):
 | 
			
		||||
    """
 | 
			
		||||
    这里的 granted nodes, 是整棵树需要的node,推算出来的也算
 | 
			
		||||
    :param user:
 | 
			
		||||
    :return:
 | 
			
		||||
    """
 | 
			
		||||
    # 获取 `UserGrantedMappingNode` 中对应的 `Node`
 | 
			
		||||
    nodes = Node.objects.filter(
 | 
			
		||||
        mapping_nodes__user=user,
 | 
			
		||||
    ).annotate(
 | 
			
		||||
        **node_annotate_mapping_node
 | 
			
		||||
    ).distinct()
 | 
			
		||||
 | 
			
		||||
    key_to_node_mapper = {}
 | 
			
		||||
    nodes_descendant_q = Q()
 | 
			
		||||
 | 
			
		||||
    for node in nodes:
 | 
			
		||||
        if not is_direct_granted_by_annotate(node):
 | 
			
		||||
            # 未授权的节点资产数量设置为 `UserGrantedMappingNode` 中的数量
 | 
			
		||||
            node.assets_amount = get_granted_assets_amount(node)
 | 
			
		||||
        else:
 | 
			
		||||
            # 直接授权的节点
 | 
			
		||||
            # 增加查询后代节点的过滤条件
 | 
			
		||||
            nodes_descendant_q |= Q(key__startswith=f'{node.key}:')
 | 
			
		||||
        key_to_node_mapper[node.key] = node
 | 
			
		||||
 | 
			
		||||
    if nodes_descendant_q:
 | 
			
		||||
        descendant_nodes = Node.objects.filter(
 | 
			
		||||
            nodes_descendant_q
 | 
			
		||||
        ).annotate(
 | 
			
		||||
            **node_annotate_set_granted
 | 
			
		||||
        )
 | 
			
		||||
        for node in descendant_nodes:
 | 
			
		||||
            key_to_node_mapper[node.key] = node
 | 
			
		||||
 | 
			
		||||
    all_nodes = key_to_node_mapper.values()
 | 
			
		||||
    return all_nodes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_user_granted_all_assets(user, via_mapping_node=True):
 | 
			
		||||
    asset_perm_ids = get_user_all_assetpermission_ids(user)
 | 
			
		||||
    if via_mapping_node:
 | 
			
		||||
        granted_node_keys = UserGrantedMappingNode.objects.filter(
 | 
			
		||||
            user=user, granted=True,
 | 
			
		||||
        ).values_list('key', flat=True).distinct()
 | 
			
		||||
    else:
 | 
			
		||||
        granted_node_keys = Node.objects.filter(
 | 
			
		||||
            granted_by_permissions__id__in=asset_perm_ids
 | 
			
		||||
        ).distinct().values_list('key', flat=True)
 | 
			
		||||
    granted_node_keys = Node.clean_children_keys(granted_node_keys)
 | 
			
		||||
 | 
			
		||||
    granted_node_q = Q()
 | 
			
		||||
    for _key in granted_node_keys:
 | 
			
		||||
        granted_node_q |= Q(nodes__key__startswith=f'{_key}:')
 | 
			
		||||
        granted_node_q |= Q(nodes__key=_key)
 | 
			
		||||
 | 
			
		||||
    assets__id = get_user_direct_granted_assets(user, asset_perm_ids).values_list('id', flat=True)
 | 
			
		||||
 | 
			
		||||
    q = granted_node_q | Q(id__in=list(assets__id))
 | 
			
		||||
    return Asset.org_objects.filter(q).distinct()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_node_all_granted_assets(user: User, key):
 | 
			
		||||
    """
 | 
			
		||||
    此算法依据 `UserGrantedMappingNode` 的数据查询
 | 
			
		||||
| 
						 | 
				
			
			@ -249,9 +326,10 @@ def get_node_all_granted_assets(user: User, key):
 | 
			
		|||
 | 
			
		||||
    # 查询该节点下的授权节点
 | 
			
		||||
    granted_mapping_nodes = UserGrantedMappingNode.objects.filter(
 | 
			
		||||
        user=user,
 | 
			
		||||
        granted=True,
 | 
			
		||||
    ).filter(Q(key__startswith=f'{key}:') | Q(key=key))
 | 
			
		||||
        user=user, granted=True,
 | 
			
		||||
    ).filter(
 | 
			
		||||
        Q(key__startswith=f'{key}:') | Q(key=key)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # 根据授权节点构建资产查询条件
 | 
			
		||||
    granted_nodes_qs = []
 | 
			
		||||
| 
						 | 
				
			
			@ -277,7 +355,7 @@ def get_node_all_granted_assets(user: User, key):
 | 
			
		|||
 | 
			
		||||
    if only_asset_granted_nodes_qs:
 | 
			
		||||
        only_asset_granted_nodes_q = reduce(or_, only_asset_granted_nodes_qs)
 | 
			
		||||
        only_asset_granted_nodes_q &= get_granted_q(user)
 | 
			
		||||
        only_asset_granted_nodes_q &= get_user_resources_q_granted_by_permissions(user)
 | 
			
		||||
        q.append(only_asset_granted_nodes_q)
 | 
			
		||||
 | 
			
		||||
    if q:
 | 
			
		||||
| 
						 | 
				
			
			@ -285,36 +363,57 @@ def get_node_all_granted_assets(user: User, key):
 | 
			
		|||
    return assets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_direct_granted_node_ids(user: User, key):
 | 
			
		||||
    granted_q = get_user_resources_q_granted_by_permissions(user)
 | 
			
		||||
 | 
			
		||||
    # 先查出该节点下的直接授权节点
 | 
			
		||||
    granted_nodes = Node.objects.filter(
 | 
			
		||||
        Q(key__startswith=f'{key}:') | Q(key=key)
 | 
			
		||||
    ).filter(granted_q).distinct().only('id', 'key')
 | 
			
		||||
 | 
			
		||||
    node_ids = set()
 | 
			
		||||
    # 根据直接授权节点查询他们的子节点
 | 
			
		||||
    q = Q()
 | 
			
		||||
    for _node in granted_nodes:
 | 
			
		||||
        q |= Q(key__startswith=f'{_node.key}:')
 | 
			
		||||
        node_ids.add(_node.id)
 | 
			
		||||
 | 
			
		||||
    if q:
 | 
			
		||||
        descendant_ids = Node.objects.filter(q).values_list('id', flat=True).distinct()
 | 
			
		||||
        node_ids.update(descendant_ids)
 | 
			
		||||
    return node_ids
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_node_all_granted_assets_from_perm(user: User, key):
 | 
			
		||||
    """
 | 
			
		||||
    此算法依据 `AssetPermission` 的数据查询
 | 
			
		||||
    1. 查询该节点下的直接授权节点
 | 
			
		||||
    2. 查询该节点下授权资产关联的节点
 | 
			
		||||
    """
 | 
			
		||||
    granted_q = get_granted_q(user)
 | 
			
		||||
 | 
			
		||||
    granted_nodes = Node.objects.filter(
 | 
			
		||||
        Q(key__startswith=f'{key}:') | Q(key=key)
 | 
			
		||||
    ).filter(granted_q).distinct()
 | 
			
		||||
 | 
			
		||||
    granted_q = get_user_resources_q_granted_by_permissions(user)
 | 
			
		||||
    # 直接授权资产查询条件
 | 
			
		||||
    granted_asset_filter_q = (Q(nodes__key__startswith=f'{key}:') | Q(nodes__key=key)) & granted_q
 | 
			
		||||
 | 
			
		||||
    # 根据授权节点构建资产查询条件
 | 
			
		||||
    q = granted_asset_filter_q
 | 
			
		||||
    for _node in granted_nodes:
 | 
			
		||||
        q |= Q(nodes__key__startswith=f'{_node.key}:')
 | 
			
		||||
        q |= Q(nodes__key=_node.key)
 | 
			
		||||
 | 
			
		||||
    q = (Q(nodes__key__startswith=f'{key}:') | Q(nodes__key=key)) & granted_q
 | 
			
		||||
    node_ids = get_direct_granted_node_ids(user, key)
 | 
			
		||||
    q |= Q(nodes__id__in=node_ids)
 | 
			
		||||
    asset_qs = Asset.objects.filter(q).distinct()
 | 
			
		||||
    return asset_qs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_direct_granted_node_assets_from_perm(user: User, key):
 | 
			
		||||
    node_ids = get_direct_granted_node_ids(user, key)
 | 
			
		||||
    asset_qs = Asset.objects.filter(nodes__id__in=node_ids).distinct()
 | 
			
		||||
    return asset_qs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def count_node_all_granted_assets(user: User, key):
 | 
			
		||||
    return get_node_all_granted_assets_from_perm(user, key).count()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_ungranted_node_children(user, key=''):
 | 
			
		||||
def count_direct_granted_node_assets(user: User, key):
 | 
			
		||||
    return get_direct_granted_node_assets_from_perm(user, key).count()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_indirect_granted_node_children(user, key=''):
 | 
			
		||||
    """
 | 
			
		||||
    获取用户授权树中未授权节点的子节点
 | 
			
		||||
    只匹配在 `UserGrantedMappingNode` 中存在的节点
 | 
			
		||||
| 
						 | 
				
			
			@ -329,6 +428,72 @@ def get_ungranted_node_children(user, key=''):
 | 
			
		|||
 | 
			
		||||
    # 设置节点授权资产数量
 | 
			
		||||
    for _node in nodes:
 | 
			
		||||
        if not is_granted(_node):
 | 
			
		||||
        if not is_direct_granted_by_annotate(_node):
 | 
			
		||||
            _node.assets_amount = get_granted_assets_amount(_node)
 | 
			
		||||
    return nodes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_top_level_granted_nodes(user):
 | 
			
		||||
    nodes = list(get_indirect_granted_node_children(user, key=''))
 | 
			
		||||
    if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
 | 
			
		||||
        ungrouped_node = get_ungrouped_node(user)
 | 
			
		||||
        nodes.insert(0, ungrouped_node)
 | 
			
		||||
    favorite_node = get_favorite_node(user)
 | 
			
		||||
    nodes.insert(0, favorite_node)
 | 
			
		||||
    return nodes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_user_all_assetpermission_ids(user: User):
 | 
			
		||||
    asset_perm_ids = set()
 | 
			
		||||
    asset_perm_ids.update(
 | 
			
		||||
        AssetPermission.objects.valid().filter(users=user).distinct().values_list('id', flat=True)
 | 
			
		||||
    )
 | 
			
		||||
    asset_perm_ids.update(
 | 
			
		||||
        AssetPermission.objects.valid().filter(user_groups__users=user).distinct().values_list('id', flat=True)
 | 
			
		||||
    )
 | 
			
		||||
    return asset_perm_ids
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_user_direct_granted_assets(user, asset_perm_ids=None):
 | 
			
		||||
    if asset_perm_ids is None:
 | 
			
		||||
        asset_perm_ids = get_user_all_assetpermission_ids(user)
 | 
			
		||||
    assets = Asset.org_objects.filter(granted_by_permissions__id__in=asset_perm_ids).distinct()
 | 
			
		||||
    return assets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def count_user_direct_granted_assets(user):
 | 
			
		||||
    count = get_user_direct_granted_assets(user).values_list('id').count()
 | 
			
		||||
    return count
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_ungrouped_node(user):
 | 
			
		||||
    assets_amount = count_user_direct_granted_assets(user)
 | 
			
		||||
    return Node(
 | 
			
		||||
        id=UNGROUPED_NODE_KEY,
 | 
			
		||||
        key=UNGROUPED_NODE_KEY,
 | 
			
		||||
        value=_(UNGROUPED_NODE_KEY),
 | 
			
		||||
        assets_amount=assets_amount
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_favorite_node(user):
 | 
			
		||||
    assets_amount = FavoriteAsset.get_user_favorite_assets(user).values_list('id').count()
 | 
			
		||||
    return Node(
 | 
			
		||||
        id=FAVORITE_NODE_KEY,
 | 
			
		||||
        key=FAVORITE_NODE_KEY,
 | 
			
		||||
        value=_(FAVORITE_NODE_KEY),
 | 
			
		||||
        assets_amount=assets_amount
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def init_user_tree_if_need(user):
 | 
			
		||||
    """
 | 
			
		||||
    升级授权树策略后,用户的数据可能还未初始化,为防止用户显示没有数据
 | 
			
		||||
    先检查 MappingNode 如果没有数据,同步创建用户授权树
 | 
			
		||||
    """
 | 
			
		||||
    if not UserGrantedMappingNode.objects.filter(user=user).exists():
 | 
			
		||||
        try:
 | 
			
		||||
            rebuild_user_mapping_nodes_with_lock(user)
 | 
			
		||||
        except lock.SomeoneIsDoingThis:
 | 
			
		||||
            # 您的数据正在初始化,请稍等
 | 
			
		||||
            raise lock.SomeoneIsDoingThis(detail=_('Please wait while your data is being initialized'))
 | 
			
		||||
							
								
								
									
										9
									
								
								jms
								
								
								
								
							
							
						
						
									
										9
									
								
								jms
								
								
								
								
							| 
						 | 
				
			
			@ -158,6 +158,7 @@ def parse_service(s):
 | 
			
		|||
    all_services = [
 | 
			
		||||
        'gunicorn', 'celery_ansible', 'celery_default',
 | 
			
		||||
        'beat', 'flower', 'daphne', 'celery_node_tree',
 | 
			
		||||
        'check_asset_perm_expired',
 | 
			
		||||
    ]
 | 
			
		||||
    if s == 'all':
 | 
			
		||||
        return all_services
 | 
			
		||||
| 
						 | 
				
			
			@ -168,7 +169,7 @@ def parse_service(s):
 | 
			
		|||
    elif s == "task":
 | 
			
		||||
        return ["celery_ansible", "celery_default", "beat"]
 | 
			
		||||
    elif s == "celery":
 | 
			
		||||
        return ["celery_ansible", "celery_default", "celery_node_tree"]
 | 
			
		||||
        return ["celery_ansible", "celery_default", "celery_node_tree", "check_asset_perm_expired"]
 | 
			
		||||
    elif "," in s:
 | 
			
		||||
        services = set()
 | 
			
		||||
        for i in s.split(','):
 | 
			
		||||
| 
						 | 
				
			
			@ -225,6 +226,11 @@ def get_start_celery_node_tree_kwargs():
 | 
			
		|||
    return get_start_worker_kwargs('node_tree', 10)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_start_celery_check_asset_perm_expired_kwargs():
 | 
			
		||||
    print("\n- Start Celery as Distributed Task Queue: CheckAseetPermissionExpired")
 | 
			
		||||
    return get_start_worker_kwargs('check_asset_perm_expired', 1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_start_worker_kwargs(queue, num):
 | 
			
		||||
    # Todo: Must set this environment, otherwise not no ansible result return
 | 
			
		||||
    os.environ.setdefault('PYTHONOPTIMIZE', '1')
 | 
			
		||||
| 
						 | 
				
			
			@ -369,6 +375,7 @@ def start_service(s):
 | 
			
		|||
        "celery_ansible": get_start_celery_ansible_kwargs,
 | 
			
		||||
        "celery_default": get_start_celery_default_kwargs,
 | 
			
		||||
        "celery_node_tree": get_start_celery_node_tree_kwargs,
 | 
			
		||||
        "check_asset_perm_expired": get_start_celery_check_asset_perm_expired_kwargs,
 | 
			
		||||
        "beat": get_start_beat_kwargs,
 | 
			
		||||
        "flower": get_start_flower_kwargs,
 | 
			
		||||
        "daphne": get_start_daphne_kwargs,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue