Compare commits

...

14 Commits
dev ... v2.4.4

Author SHA1 Message Date
xinwen
19a8a0806e fix(perms): 当用户授权为空时,清空旧的授权树 2020-11-09 03:27:44 -06:00
xinwen
49c17d18b3 fix(perms): 由于组织不对,导致生成或显示授权树错误 2020-11-06 06:57:10 -06:00
xinwen
02b8533ea0 perf(perms): 优化授权树生成速度 2020-11-06 15:50:46 +08:00
xinwen
5c46ff20de fix(orgs): org-member-relation url 拼写错误 2020-11-03 17:58:13 +08:00
xinwen
074121b957 fix(perms): 重建授权树冲突时,响应里加 code 2020-11-03 17:53:20 +08:00
xinwen
0eff79c47a perf(perms): 优化用户授权资产列表加载速度 2020-10-31 09:54:07 +08:00
xinwen
10ff7730ec perf(perms): 优化根据资产获取授权的系统用户 2020-10-30 00:38:05 -05:00
root
4f70e313b4 modify: AttributeError: 'UUID' object has no attribute 'name' 2020-10-26 06:18:25 -05:00
xinwen
c225fd584d fix(assets): 系统用户与用户组发生变化时报错 2020-10-26 05:30:46 -05:00
xinwen
8ed6a64a9e fix(assets): 资产列表添加默认 date_created 排序 2020-10-23 02:24:24 -05:00
ibuler
d7e97629aa fix(perms): 修复asset permission导入的bug 2020-10-20 14:50:36 +08:00
ibuler
2c20889540 fix(perms): 修复用户的资产不区分组织的问题 2020-10-19 21:50:31 -05:00
Bai
7d325856b9 perf(config): 升级依赖redis==3.5.3; 添加CACHES配置: health_check_interval=30; 解决因网络不稳定导致的redis连接失败异常 2020-10-19 04:45:01 -05:00
xinwen
28879d9314 fix(orgs): 更新用户时org_roles参数为None时不更新组织角色 2020-10-18 22:23:27 -05:00
17 changed files with 94 additions and 87 deletions

View File

@@ -355,3 +355,4 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
class Meta: class Meta:
unique_together = [('org_id', 'hostname')] unique_together = [('org_id', 'hostname')]
verbose_name = _("Asset") verbose_name = _("Asset")
ordering = ['-date_created']

View File

@@ -140,7 +140,7 @@ def on_system_user_groups_change(instance, action, pk_set, reverse, **kwargs):
logger.info("System user groups update signal recv: {}".format(instance)) logger.info("System user groups update signal recv: {}".format(instance))
users = User.objects.filter(groups__id__in=pk_set).distinct() users = User.objects.filter(groups__id__in=pk_set).distinct()
instance.users.add(users) instance.users.add(*users)
@receiver(m2m_changed, sender=Asset.nodes.through) @receiver(m2m_changed, sender=Asset.nodes.through)

View File

@@ -241,8 +241,8 @@ def push_system_user_a_asset_manual(system_user, asset, username=None):
@shared_task(queue="ansible") @shared_task(queue="ansible")
def push_system_user_to_assets(system_user, assets, username=None): def push_system_user_to_assets(system_user, assets, username=None):
task_name = _("Push system users to assets: {}").format(system_user.name)
system_user = get_object_if_need(SystemUser, system_user) system_user = get_object_if_need(SystemUser, system_user)
task_name = _("Push system users to assets: {}").format(system_user.name)
assets = get_objects_if_need(Asset, assets) assets = get_objects_if_need(Asset, assets)
return push_system_user_util(system_user, assets, task_name, username=username) return push_system_user_util(system_user, assets, task_name, username=username)

View File

@@ -38,10 +38,10 @@ def common_exception_handler(exc, context):
if getattr(exc, 'wait', None): if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait headers['Retry-After'] = '%d' % exc.wait
if isinstance(exc.detail, (list, dict)): if isinstance(exc.detail, str) and isinstance(exc.get_codes(), str):
data = exc.detail data = {'detail': exc.detail, 'code': exc.get_codes()}
else: else:
data = {'detail': exc.detail} data = exc.detail
set_rollback() set_rollback()
return Response(data, status=exc.status_code, headers=headers) return Response(data, status=exc.status_code, headers=headers)

View File

@@ -242,6 +242,9 @@ CACHES = {
'host': CONFIG.REDIS_HOST, 'host': CONFIG.REDIS_HOST,
'port': CONFIG.REDIS_PORT, 'port': CONFIG.REDIS_PORT,
'db': CONFIG.REDIS_DB_CACHE, 'db': CONFIG.REDIS_DB_CACHE,
},
'OPTIONS': {
"REDIS_CLIENT_KWARGS": {"health_check_interval": 30}
} }
} }
} }

View File

@@ -14,7 +14,7 @@ router = DefaultRouter()
bulk_router = BulkRouter() bulk_router = BulkRouter()
router.register(r'orgs', api.OrgViewSet, 'org') router.register(r'orgs', api.OrgViewSet, 'org')
bulk_router.register(r'org-memeber-relation', api.OrgMemberRelationBulkViewSet, 'org-memeber-relation') bulk_router.register(r'org-member-relation', api.OrgMemberRelationBulkViewSet, 'org-member-relation')
old_version_urlpatterns = [ old_version_urlpatterns = [
re_path('(?P<resource>org)/.*', capi.redirect_plural_name_api) re_path('(?P<resource>org)/.*', capi.redirect_plural_name_api)

View File

@@ -3,7 +3,7 @@
from django.db.models import Q from django.db.models import Q
from common.permissions import IsOrgAdmin from common.permissions import IsOrgAdmin
from orgs.mixins.api import OrgModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from common.utils import get_object_or_none from common.utils import get_object_or_none
from ..models import AssetPermission from ..models import AssetPermission
from ..hands import ( from ..hands import (
@@ -17,7 +17,7 @@ __all__ = [
] ]
class AssetPermissionViewSet(OrgModelViewSet): class AssetPermissionViewSet(OrgBulkModelViewSet):
""" """
资产授权列表的增删改查api 资产授权列表的增删改查api
""" """

View File

@@ -4,6 +4,7 @@ from rest_framework.request import Request
from common.permissions import IsOrgAdminOrAppUser, IsValidUser from common.permissions import IsOrgAdminOrAppUser, IsValidUser
from common.utils import lazyproperty from common.utils import lazyproperty
from orgs.utils import tmp_to_root_org
from users.models import User from users.models import User
from perms.models import UserGrantedMappingNode from perms.models import UserGrantedMappingNode
@@ -47,6 +48,10 @@ class ForUserMixin:
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
request: Request request: Request
def get(self, request, *args, **kwargs):
with tmp_to_root_org():
return super().get(request, *args, **kwargs)
@lazyproperty @lazyproperty
def user(self): def user(self):
return self.request.user return self.request.user

View File

@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils.decorators import method_decorator
from perms.api.user_permission.mixin import UserNodeGrantStatusDispatchMixin from perms.api.user_permission.mixin import UserNodeGrantStatusDispatchMixin
from rest_framework.generics import ListAPIView from rest_framework.generics import ListAPIView
from rest_framework.response import Response from rest_framework.response import Response
@@ -10,7 +9,6 @@ from assets.api.mixin import SerializeToTreeNodeMixin
from common.utils import get_logger from common.utils import get_logger
from perms.pagination import GrantedAssetLimitOffsetPagination from perms.pagination import GrantedAssetLimitOffsetPagination
from assets.models import Asset, Node, FavoriteAsset from assets.models import Asset, Node, FavoriteAsset
from orgs.utils import tmp_to_root_org
from ... import serializers from ... import serializers
from ...utils.user_asset_permission import ( from ...utils.user_asset_permission import (
get_node_all_granted_assets, get_user_direct_granted_assets, get_node_all_granted_assets, get_user_direct_granted_assets,
@@ -22,7 +20,6 @@ from .mixin import ForAdminMixin, ForUserMixin
logger = get_logger(__name__) logger = get_logger(__name__)
@method_decorator(tmp_to_root_org(), name='list')
class UserDirectGrantedAssetsApi(ListAPIView): class UserDirectGrantedAssetsApi(ListAPIView):
""" """
用户直接授权的资产的列表,也就是授权规则上直接授权的资产,并非是来自节点的 用户直接授权的资产的列表,也就是授权规则上直接授权的资产,并非是来自节点的
@@ -40,7 +37,6 @@ class UserDirectGrantedAssetsApi(ListAPIView):
return assets return assets
@method_decorator(tmp_to_root_org(), name='list')
class UserFavoriteGrantedAssetsApi(ListAPIView): class UserFavoriteGrantedAssetsApi(ListAPIView):
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetGrantedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
@@ -55,7 +51,6 @@ class UserFavoriteGrantedAssetsApi(ListAPIView):
return assets return assets
@method_decorator(tmp_to_root_org(), name='list')
class AssetsAsTreeMixin(SerializeToTreeNodeMixin): class AssetsAsTreeMixin(SerializeToTreeNodeMixin):
""" """
将 资产 序列化成树的结构返回 将 资产 序列化成树的结构返回
@@ -82,12 +77,10 @@ class MyFavoriteGrantedAssetsApi(ForUserMixin, UserFavoriteGrantedAssetsApi):
pass pass
@method_decorator(tmp_to_root_org(), name='list')
class UserDirectGrantedAssetsAsTreeForAdminApi(ForAdminMixin, AssetsAsTreeMixin, UserDirectGrantedAssetsApi): class UserDirectGrantedAssetsAsTreeForAdminApi(ForAdminMixin, AssetsAsTreeMixin, UserDirectGrantedAssetsApi):
pass pass
@method_decorator(tmp_to_root_org(), name='list')
class MyUngroupAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserDirectGrantedAssetsApi): class MyUngroupAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserDirectGrantedAssetsApi):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
@@ -96,9 +89,11 @@ class MyUngroupAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserDirectGrante
return queryset return queryset
@method_decorator(tmp_to_root_org(), name='list') class UserAllGrantedAssetsApi(ForAdminMixin, ListAPIView):
class UserAllGrantedAssetsApi(ListAPIView):
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
serializer_class = serializers.AssetGrantedSerializer
filter_fields = ['hostname', 'ip', 'id', 'comment']
search_fields = ['hostname', 'ip', 'comment']
def get_queryset(self): def get_queryset(self):
queryset = get_user_granted_all_assets(self.user) queryset = get_user_granted_all_assets(self.user)
@@ -106,11 +101,14 @@ class UserAllGrantedAssetsApi(ListAPIView):
return queryset.only(*self.only_fields) return queryset.only(*self.only_fields)
class MyAllGrantedAssetsApi(ForUserMixin, UserAllGrantedAssetsApi):
pass
class MyAllAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserAllGrantedAssetsApi): class MyAllAssetsAsTreeApi(ForUserMixin, AssetsAsTreeMixin, UserAllGrantedAssetsApi):
search_fields = ['hostname', 'ip'] search_fields = ['hostname', 'ip']
@method_decorator(tmp_to_root_org(), name='list')
class UserGrantedNodeAssetsApi(UserNodeGrantStatusDispatchMixin, ListAPIView): class UserGrantedNodeAssetsApi(UserNodeGrantStatusDispatchMixin, ListAPIView):
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetGrantedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields only_fields = serializers.AssetGrantedSerializer.Meta.only_fields

View File

@@ -7,7 +7,6 @@ from rest_framework.generics import (
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.request import Request from rest_framework.request import Request
from orgs.utils import tmp_to_root_org
from assets.api.mixin import SerializeToTreeNodeMixin from assets.api.mixin import SerializeToTreeNodeMixin
from common.utils import get_logger from common.utils import get_logger
from .mixin import ForAdminMixin, ForUserMixin, UserNodeGrantStatusDispatchMixin from .mixin import ForAdminMixin, ForUserMixin, UserNodeGrantStatusDispatchMixin
@@ -59,7 +58,6 @@ class NodeChildrenMixin:
class BaseGrantedNodeApi(_GrantedNodeStructApi, metaclass=abc.ABCMeta): class BaseGrantedNodeApi(_GrantedNodeStructApi, metaclass=abc.ABCMeta):
serializer_class = serializers.NodeGrantedSerializer serializer_class = serializers.NodeGrantedSerializer
@tmp_to_root_org()
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
rebuild_user_tree_if_need(request, self.user) rebuild_user_tree_if_need(request, self.user)
nodes = self.get_nodes() nodes = self.get_nodes()
@@ -72,7 +70,6 @@ class BaseNodeChildrenApi(NodeChildrenMixin, BaseGrantedNodeApi, metaclass=abc.A
class BaseGrantedNodeAsTreeApi(SerializeToTreeNodeMixin, _GrantedNodeStructApi, metaclass=abc.ABCMeta): class BaseGrantedNodeAsTreeApi(SerializeToTreeNodeMixin, _GrantedNodeStructApi, metaclass=abc.ABCMeta):
@tmp_to_root_org()
def list(self, request: Request, *args, **kwargs): def list(self, request: Request, *args, **kwargs):
rebuild_user_tree_if_need(request, self.user) rebuild_user_tree_if_need(request, self.user)
nodes = self.get_nodes() nodes = self.get_nodes()

View File

@@ -9,12 +9,11 @@ from common.permissions import IsValidUser
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from .mixin import UserNodeGrantStatusDispatchMixin, ForUserMixin, ForAdminMixin from .mixin import UserNodeGrantStatusDispatchMixin, ForUserMixin, ForAdminMixin
from ...utils.user_asset_permission import ( 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_indirect_granted_node_children, UNGROUPED_NODE_KEY, FAVORITE_NODE_KEY,
get_user_direct_granted_assets, get_top_level_granted_nodes, get_user_direct_granted_assets, get_top_level_granted_nodes,
get_user_granted_nodes_list_via_mapping_node, get_user_granted_nodes_list_via_mapping_node,
get_user_granted_all_assets, rebuild_user_tree_if_need, get_user_granted_all_assets, rebuild_user_tree_if_need,
get_user_all_assetpermission_ids, get_user_all_assetpermissions_id,
) )
from assets.models import Asset, FavoriteAsset from assets.models import Asset, FavoriteAsset
@@ -66,7 +65,7 @@ class UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi(ForAdminMixin, UserNode
def get_data_on_node_indirect_granted(self, key): def get_data_on_node_indirect_granted(self, key):
user = self.user user = self.user
asset_perm_ids = get_user_all_assetpermission_ids(user) asset_perm_ids = get_user_all_assetpermissions_id(user)
nodes = get_indirect_granted_node_children(user, key) nodes = get_indirect_granted_node_children(user, key)
@@ -102,7 +101,6 @@ class UserGrantedNodeChildrenWithAssetsAsTreeForAdminApi(ForAdminMixin, UserNode
if node: if node:
return node.key return node.key
@tmp_to_root_org()
def list(self, request: Request, *args, **kwargs): def list(self, request: Request, *args, **kwargs):
key = self.request.query_params.get('key') key = self.request.query_params.get('key')
if key is None: if key is None:

View File

@@ -43,12 +43,14 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
model = AssetPermission model = AssetPermission
mini_fields = ['id', 'name'] mini_fields = ['id', 'name']
small_fields = mini_fields + [ small_fields = mini_fields + [
'is_active', 'is_expired', 'is_valid', 'actions', 'created_by', 'date_created', 'is_active', 'is_expired', 'is_valid', 'actions',
'date_expired', 'date_start', 'comment' 'created_by', 'date_created', 'date_expired',
'date_start', 'comment'
] ]
m2m_fields = [ m2m_fields = [
'users', 'user_groups', 'assets', 'nodes', 'system_users', 'users', 'user_groups', 'assets', 'nodes', 'system_users',
'users_amount', 'user_groups_amount', 'assets_amount', 'nodes_amount', 'system_users_amount', 'users_amount', 'user_groups_amount', 'assets_amount',
'nodes_amount', 'system_users_amount',
] ]
fields = small_fields + m2m_fields fields = small_fields + m2m_fields
read_only_fields = ['created_by', 'date_created'] read_only_fields = ['created_by', 'date_created']

View File

@@ -19,11 +19,9 @@ user_permission_urlpatterns = [
# 直接授权:在 `AssetPermission` 中关联的对象 # 直接授权:在 `AssetPermission` 中关联的对象
# --------------------------------------------------------- # ---------------------------------------------------------
# 获取用户所有直接授权的资产
# 以 serializer 格式返回 # 以 serializer 格式返回
path('<uuid:pk>/assets/', api.UserDirectGrantedAssetsForAdminApi.as_view(), name='user-assets'), path('<uuid:pk>/assets/', api.UserAllGrantedAssetsApi.as_view(), name='user-assets'),
path('assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-assets'), path('assets/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets'),
# Tree Node 的数据格式返回 # Tree Node 的数据格式返回
path('<uuid:pk>/assets/tree/', api.UserDirectGrantedAssetsAsTreeForAdminApi.as_view(), name='user-assets-as-tree'), path('<uuid:pk>/assets/tree/', api.UserDirectGrantedAssetsAsTreeForAdminApi.as_view(), name='user-assets-as-tree'),

View File

@@ -11,16 +11,19 @@ logger = get_logger(__file__)
def get_asset_system_users_id_with_actions(asset_perm_queryset: BasePermissionQuerySet, asset: Asset): def get_asset_system_users_id_with_actions(asset_perm_queryset: BasePermissionQuerySet, asset: Asset):
asset_perms_id = set(asset_perm_queryset.values_list('id', flat=True))
nodes = asset.get_nodes() nodes = asset.get_nodes()
node_keys = set() node_keys = set()
for node in nodes: for node in nodes:
ancestor_keys = node.get_ancestor_keys(with_self=True) ancestor_keys = node.get_ancestor_keys(with_self=True)
node_keys.update(ancestor_keys) node_keys.update(ancestor_keys)
queryset = asset_perm_queryset.filter( queryset = AssetPermission.objects.filter(id__in=asset_perms_id).filter(
Q(assets=asset) | Q(assets=asset) |
Q(nodes__key__in=node_keys) Q(nodes__key__in=node_keys)
) )
asset_protocols = asset.protocols_as_dict.keys() asset_protocols = asset.protocols_as_dict.keys()
values = queryset.filter( values = queryset.filter(
system_users__protocol__in=asset_protocols system_users__protocol__in=asset_protocols

View File

@@ -1,5 +1,5 @@
from functools import reduce, wraps from functools import reduce, wraps
from operator import or_, and_ from operator import or_
from uuid import uuid4 from uuid import uuid4
import threading import threading
import inspect import inspect
@@ -32,27 +32,6 @@ TMP_ASSET_GRANTED_FIELD = '_asset_granted'
TMP_GRANTED_ASSETS_AMOUNT_FIELD = '_granted_assets_amount' TMP_GRANTED_ASSETS_AMOUNT_FIELD = '_granted_assets_amount'
# 使用场景
# 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)
)
))
# 使用场景 # 使用场景
# `Node.objects.annotate(**node_annotate_mapping_node)` # `Node.objects.annotate(**node_annotate_mapping_node)`
node_annotate_mapping_node = { node_annotate_mapping_node = {
@@ -147,12 +126,15 @@ def rebuild_user_mapping_nodes_with_lock(user: User):
@tmp_to_root_org() @tmp_to_root_org()
def compute_tmp_mapping_node_from_perm(user: User): def compute_tmp_mapping_node_from_perm(user: User, asset_perms_id=None):
node_only_fields = ('id', 'key', 'parent_key', 'assets_amount') node_only_fields = ('id', 'key', 'parent_key', 'assets_amount')
if asset_perms_id is None:
asset_perms_id = get_user_all_assetpermissions_id(user)
# 查询直接授权节点 # 查询直接授权节点
nodes = Node.objects.filter( nodes = Node.objects.filter(
get_user_resources_q_granted_by_permissions(user) granted_by_permissions__id__in=asset_perms_id
).distinct().only(*node_only_fields) ).distinct().only(*node_only_fields)
granted_key_set = {_node.key for _node in nodes} granted_key_set = {_node.key for _node in nodes}
@@ -178,7 +160,7 @@ def compute_tmp_mapping_node_from_perm(user: User):
def process_direct_granted_assets(): def process_direct_granted_assets():
# 查询直接授权资产 # 查询直接授权资产
asset_ids = Asset.objects.filter( asset_ids = Asset.objects.filter(
get_user_resources_q_granted_by_permissions(user) granted_by_permissions__id__in=asset_perms_id
).distinct().values_list('id', flat=True) ).distinct().values_list('id', flat=True)
# 查询授权资产关联的节点设置 # 查询授权资产关联的节点设置
granted_asset_nodes = Node.objects.filter( granted_asset_nodes = Node.objects.filter(
@@ -211,7 +193,7 @@ def compute_tmp_mapping_node_from_perm(user: User):
return [*leaf_nodes, *ancestors] return [*leaf_nodes, *ancestors]
def create_mapping_nodes(user, nodes, clear=True): def create_mapping_nodes(user, nodes):
to_create = [] to_create = []
for node in nodes: for node in nodes:
_granted = getattr(node, TMP_GRANTED_FIELD, False) _granted = getattr(node, TMP_GRANTED_FIELD, False)
@@ -227,12 +209,10 @@ def create_mapping_nodes(user, nodes, clear=True):
assets_amount=_granted_assets_amount, assets_amount=_granted_assets_amount,
)) ))
if clear:
UserGrantedMappingNode.objects.filter(user=user).delete()
UserGrantedMappingNode.objects.bulk_create(to_create) UserGrantedMappingNode.objects.bulk_create(to_create)
def set_node_granted_assets_amount(user, node): def set_node_granted_assets_amount(user, node, asset_perms_id=None):
""" """
不依赖`UserGrantedMappingNode`直接查询授权计算资产数量 不依赖`UserGrantedMappingNode`直接查询授权计算资产数量
""" """
@@ -241,17 +221,25 @@ def set_node_granted_assets_amount(user, node):
assets_amount = node.assets_amount assets_amount = node.assets_amount
else: else:
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
assets_amount = count_direct_granted_node_assets(user, node.key) assets_amount = count_direct_granted_node_assets(user, node.key, asset_perms_id)
else: else:
assets_amount = count_node_all_granted_assets(user, node.key) assets_amount = count_node_all_granted_assets(user, node.key, asset_perms_id)
setattr(node, TMP_GRANTED_ASSETS_AMOUNT_FIELD, assets_amount) setattr(node, TMP_GRANTED_ASSETS_AMOUNT_FIELD, assets_amount)
@tmp_to_root_org()
def rebuild_user_mapping_nodes(user): def rebuild_user_mapping_nodes(user):
logger.info(f'>>> {dt_formater(now())} start rebuild {user} mapping nodes') logger.info(f'>>> {dt_formater(now())} start rebuild {user} mapping nodes')
tmp_nodes = compute_tmp_mapping_node_from_perm(user)
# 先删除用户旧的授权树🌲
UserGrantedMappingNode.objects.filter(user=user).delete()
asset_perms_id = get_user_all_assetpermissions_id(user)
if not asset_perms_id:
# 没有授权直接返回
return
tmp_nodes = compute_tmp_mapping_node_from_perm(user, asset_perms_id)
for _node in tmp_nodes: for _node in tmp_nodes:
set_node_granted_assets_amount(user, _node) set_node_granted_assets_amount(user, _node, asset_perms_id)
create_mapping_nodes(user, tmp_nodes) create_mapping_nodes(user, tmp_nodes)
logger.info(f'>>> {dt_formater(now())} end rebuild {user} mapping nodes') logger.info(f'>>> {dt_formater(now())} end rebuild {user} mapping nodes')
@@ -303,7 +291,7 @@ def get_user_granted_nodes_list_via_mapping_node(user):
def get_user_granted_all_assets(user, via_mapping_node=True): def get_user_granted_all_assets(user, via_mapping_node=True):
asset_perm_ids = get_user_all_assetpermission_ids(user) asset_perm_ids = get_user_all_assetpermissions_id(user)
if via_mapping_node: if via_mapping_node:
granted_node_keys = UserGrantedMappingNode.objects.filter( granted_node_keys = UserGrantedMappingNode.objects.filter(
user=user, granted=True, user=user, granted=True,
@@ -365,7 +353,8 @@ def get_node_all_granted_assets(user: User, key):
if only_asset_granted_nodes_qs: if only_asset_granted_nodes_qs:
only_asset_granted_nodes_q = reduce(or_, only_asset_granted_nodes_qs) only_asset_granted_nodes_q = reduce(or_, only_asset_granted_nodes_qs)
only_asset_granted_nodes_q &= get_user_resources_q_granted_by_permissions(user) asset_perms_id = get_user_all_assetpermissions_id(user)
only_asset_granted_nodes_q &= Q(granted_by_permissions__id__in=asset_perms_id)
q.append(only_asset_granted_nodes_q) q.append(only_asset_granted_nodes_q)
if q: if q:
@@ -373,13 +362,16 @@ def get_node_all_granted_assets(user: User, key):
return assets return assets
def get_direct_granted_node_ids(user: User, key): def get_direct_granted_node_ids(user: User, key, asset_perms_id=None):
granted_q = get_user_resources_q_granted_by_permissions(user) if asset_perms_id is None:
asset_perms_id = get_user_all_assetpermissions_id(user)
# 先查出该节点下的直接授权节点 # 先查出该节点下的直接授权节点
granted_nodes = Node.objects.filter( granted_nodes = Node.objects.filter(
Q(key__startswith=f'{key}:') | Q(key=key) Q(key__startswith=f'{key}:') | Q(key=key)
).filter(granted_q).distinct().only('id', 'key') ).filter(
granted_by_permissions__id__in=asset_perms_id
).distinct().only('id', 'key')
node_ids = set() node_ids = set()
# 根据直接授权节点查询他们的子节点 # 根据直接授权节点查询他们的子节点
@@ -394,33 +386,38 @@ def get_direct_granted_node_ids(user: User, key):
return node_ids return node_ids
def get_node_all_granted_assets_from_perm(user: User, key): def get_node_all_granted_assets_from_perm(user: User, key, asset_perms_id=None):
""" """
此算法依据 `AssetPermission` 的数据查询 此算法依据 `AssetPermission` 的数据查询
1. 查询该节点下的直接授权节点 1. 查询该节点下的直接授权节点
2. 查询该节点下授权资产关联的节点 2. 查询该节点下授权资产关联的节点
""" """
granted_q = get_user_resources_q_granted_by_permissions(user) if asset_perms_id is None:
asset_perms_id = get_user_all_assetpermissions_id(user)
# 直接授权资产查询条件 # 直接授权资产查询条件
q = (Q(nodes__key__startswith=f'{key}:') | Q(nodes__key=key)) & granted_q q = (
node_ids = get_direct_granted_node_ids(user, key) Q(nodes__key__startswith=f'{key}:') | Q(nodes__key=key)
) & Q(granted_by_permissions__id__in=asset_perms_id)
node_ids = get_direct_granted_node_ids(user, key, asset_perms_id)
q |= Q(nodes__id__in=node_ids) q |= Q(nodes__id__in=node_ids)
asset_qs = Asset.objects.filter(q).distinct() asset_qs = Asset.objects.filter(q).distinct()
return asset_qs return asset_qs
def get_direct_granted_node_assets_from_perm(user: User, key): def get_direct_granted_node_assets_from_perm(user: User, key, asset_perms_id=None):
node_ids = get_direct_granted_node_ids(user, key) node_ids = get_direct_granted_node_ids(user, key, asset_perms_id)
asset_qs = Asset.objects.filter(nodes__id__in=node_ids).distinct() asset_qs = Asset.objects.filter(nodes__id__in=node_ids).distinct()
return asset_qs return asset_qs
def count_node_all_granted_assets(user: User, key): def count_node_all_granted_assets(user: User, key, asset_perms_id=None):
return get_node_all_granted_assets_from_perm(user, key).count() return get_node_all_granted_assets_from_perm(user, key, asset_perms_id).count()
def count_direct_granted_node_assets(user: User, key): def count_direct_granted_node_assets(user: User, key, asset_perms_id=None):
return get_direct_granted_node_assets_from_perm(user, key).count() return get_direct_granted_node_assets_from_perm(user, key, asset_perms_id).count()
def get_indirect_granted_node_children(user, key=''): def get_indirect_granted_node_children(user, key=''):
@@ -453,7 +450,7 @@ def get_top_level_granted_nodes(user):
return nodes return nodes
def get_user_all_assetpermission_ids(user: User): def get_user_all_assetpermissions_id(user: User):
asset_perm_ids = set() asset_perm_ids = set()
asset_perm_ids.update( asset_perm_ids.update(
AssetPermission.objects.valid().filter(users=user).distinct().values_list('id', flat=True) AssetPermission.objects.valid().filter(users=user).distinct().values_list('id', flat=True)
@@ -466,7 +463,7 @@ def get_user_all_assetpermission_ids(user: User):
def get_user_direct_granted_assets(user, asset_perm_ids=None): def get_user_direct_granted_assets(user, asset_perm_ids=None):
if asset_perm_ids is None: if asset_perm_ids is None:
asset_perm_ids = get_user_all_assetpermission_ids(user) asset_perm_ids = get_user_all_assetpermissions_id(user)
assets = Asset.org_objects.filter(granted_by_permissions__id__in=asset_perm_ids).distinct() assets = Asset.org_objects.filter(granted_by_permissions__id__in=asset_perm_ids).distinct()
return assets return assets
@@ -507,4 +504,7 @@ def rebuild_user_tree_if_need(request, user):
rebuild_user_mapping_nodes_with_lock(user) rebuild_user_mapping_nodes_with_lock(user)
except lock.SomeoneIsDoingThis: except lock.SomeoneIsDoingThis:
# 您的数据正在初始化,请稍等 # 您的数据正在初始化,请稍等
raise lock.SomeoneIsDoingThis(detail=_('Please wait while your data is being initialized')) raise lock.SomeoneIsDoingThis(
detail=_('Please wait while your data is being initialized'),
code='rebuild_tree_conflict'
)

View File

@@ -55,11 +55,13 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
post_user_create.send(self.__class__, user=user) post_user_create.send(self.__class__, user=user)
@staticmethod @staticmethod
def set_users_to_org(users, org_roles): def set_users_to_org(users, org_roles, update=False):
# 只有真实存在的组织才真正关联用户 # 只有真实存在的组织才真正关联用户
if not current_org or not current_org.is_real(): if not current_org or not current_org.is_real():
return return
for user, roles in zip(users, org_roles): for user, roles in zip(users, org_roles):
if update and roles is None:
continue
if not roles: if not roles:
# 当前组织创建的用户,至少是该组织的`User` # 当前组织创建的用户,至少是该组织的`User`
roles = [ORG_ROLE.USER] roles = [ORG_ROLE.USER]
@@ -107,7 +109,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
users = serializer.save() users = serializer.save()
if isinstance(users, User): if isinstance(users, User):
users = [users] users = [users]
self.set_users_to_org(users, org_roles) self.set_users_to_org(users, org_roles, update=True)
def perform_bulk_update(self, serializer): def perform_bulk_update(self, serializer):
# TODO: 需要测试 # TODO: 需要测试

View File

@@ -59,7 +59,7 @@ python-dateutil==2.6.1
python-gssapi==0.6.4 python-gssapi==0.6.4
pytz==2018.3 pytz==2018.3
PyYAML==5.1 PyYAML==5.1
redis==3.2.0 redis==3.5.3
requests==2.22.0 requests==2.22.0
jms-storage==0.0.34 jms-storage==0.0.34
s3transfer==0.3.3 s3transfer==0.3.3