From 8775cb37d9348fc2e9b5287c714b46a51b3e9b22 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 25 Aug 2026 09:01:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(asset):=20=E5=85=AC=E5=8F=B8=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=9D=83=E9=99=90=E6=94=B6=E6=95=9B=E2=80=94=E2=80=94?= =?UTF-8?q?=E4=BB=85=E7=AE=A1=E7=90=86=E5=91=98=E5=8F=AF=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 菜单:'公司资产'目录仅 IsCompanyAdmin(admin:company/tenant/super)可见,普通用户隐藏 - 数据范围:FileAssetViewSet 按角色——admin:super 全部 / admin:tenant 本租户所有公司 / admin:company 本公司 / 其他 403 - 上传:Init 管理员校验;Complete/SimpleUpload 归属公司强制解析(租户管理员可指定本租户公司,公司管理员仅本公司) --- gvsds/apps/gerp/views/asset.py | 45 +++++++++++++++++++++++++++++-- gvsds/apps/gerp/views/menu.py | 9 +++++++ gvsds/apps/gerp/views/viewsets.py | 30 +++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/gvsds/apps/gerp/views/asset.py b/gvsds/apps/gerp/views/asset.py index 02b4cbf4..a14249c2 100644 --- a/gvsds/apps/gerp/views/asset.py +++ b/gvsds/apps/gerp/views/asset.py @@ -11,10 +11,51 @@ from gvsdsdk.model_utils import UUIDObj from . import ELTAuthenticatedAPIView +def _RequireAssetAdmin(request): + """公司资产上传仅公司管理员/租户管理员可用""" + from rest_framework.exceptions import PermissionDenied + from gvsdsdk.auth.admin_service import IsCompanyAdmin + if not IsCompanyAdmin(request=request): + raise PermissionDenied('需要公司管理员或租户管理员权限') + + +def _ResolveAssetCompany(request): + """上传归属公司解析:仅公司管理员/租户管理员可用。 + - 租户管理员:可指定本租户任意公司;不传则用当前公司上下文 + - 公司管理员:仅本公司(前端传入必须匹配) + - 其他用户:403 + """ + from rest_framework.exceptions import PermissionDenied + from gvsdsdk.auth.admin_service import IsTenantAdmin, IsCompanyAdmin + from gvsdsdk.models import Company as SdkCompany + if not IsCompanyAdmin(request=request): + raise PermissionDenied('需要公司管理员或租户管理员权限') + claimed = (request.data.get('CompanyUUID') or '').strip() + claimed_bytes = UUIDObj(claimed).bytes if claimed else None + cur_company = getattr(request, 'current_company_uuid', None) + if IsTenantAdmin(request=request): + tenant_uuid = getattr(request, 'current_tenant_uuid', None) + if claimed_bytes: + tb = UUIDObj(tenant_uuid).bytes if tenant_uuid else None + if tb is None or not SdkCompany.objects.filter(CompanyUUID=claimed_bytes, TenantUUID=tb).exists(): + raise PermissionDenied('目标公司不在本租户') + return claimed_bytes + if cur_company: + return UUIDObj(cur_company).bytes + raise PermissionDenied('缺少 CompanyUUID') + if cur_company: + cb = UUIDObj(cur_company).bytes + if claimed_bytes and claimed_bytes != cb: + raise PermissionDenied('仅可上传到本公司') + return cb + raise PermissionDenied('缺少公司上下文') + + class AssetUploadInitView(ELTAuthenticatedAPIView): """上传初始化视图——创建分片上传会话""" def post(self, request): + _RequireAssetAdmin(request) file_name = request.data.get('FileName', '') file_size = request.data.get('FileSize', 0) mime_type = request.data.get('MimeType', 'application/octet-stream') @@ -134,7 +175,7 @@ class AssetUploadCompleteView(ELTAuthenticatedAPIView): total_size += len(data) file_sha1 = sha1.hexdigest() user_uuid = request.user.UserUUID - company_uuid = request.data.get('CompanyUUID') + company_uuid = _ResolveAssetCompany(request) from gvsdsdk.auth.managers import AssetManager asset, created = AssetManager.EnsureAsset( file_sha1, file_name, total_size, mime_type, final_path, @@ -182,7 +223,7 @@ class AssetSimpleUploadView(ELTAuthenticatedAPIView): file_name = upload_file.name mime_type = upload_file.content_type or 'application/octet-stream' user_uuid = request.user.UserUUID - company_uuid = request.data.get('CompanyUUID') + company_uuid = _ResolveAssetCompany(request) sha1 = hashlib.sha1() final_dir = os.path.join(settings.MEDIA_ROOT, 'assets') diff --git a/gvsds/apps/gerp/views/menu.py b/gvsds/apps/gerp/views/menu.py index 13144243..091d0926 100644 --- a/gvsds/apps/gerp/views/menu.py +++ b/gvsds/apps/gerp/views/menu.py @@ -128,6 +128,15 @@ class MenuListCompatView(ELTAuthenticatedAPIView): perm_attr_str = item['PermAttr'] perm_attrs = [a.strip() for a in perm_attr_str.split(';') if a.strip()] if perm_attr_str else [] if not perm_attrs: + # 公司资产目录:仅公司管理员/租户管理员可见(普通用户隐藏) + if item['Title'] == '公司资产': + from gvsdsdk.auth.admin_service import IsCompanyAdmin + try: + if not IsCompanyAdmin(request=request, user=user, role_uuid=role_uuid_bytes): + no_perm_uuids.add(uuid_str) + except Exception: + logger.exception('MenuListCompatView 公司资产菜单校验异常 uuid=%s', uuid_str) + no_perm_uuids.add(uuid_str) continue if 'admin:super' in perm_attrs: from gvsdsdk.auth.admin_service import IsSuperAdmin diff --git a/gvsds/apps/gerp/views/viewsets.py b/gvsds/apps/gerp/views/viewsets.py index 43673a39..b8b9a26c 100644 --- a/gvsds/apps/gerp/views/viewsets.py +++ b/gvsds/apps/gerp/views/viewsets.py @@ -574,6 +574,36 @@ class FileAssetViewSet(CompanyScopedMixin, viewsets.ModelViewSet): serializer_class = FileAssetSerializer permission_classes = [IsAuthenticated] + def get_queryset(self): + """公司资产数据范围: + - 跨租户管理员(admin:super):全部 + - 租户管理员(admin:tenant):本租户所有公司资产 + - 公司管理员(admin:company):本公司资产 + - 其他用户:无权限(403) + """ + from rest_framework.exceptions import PermissionDenied + from gvsdsdk.auth.admin_service import IsSuperAdmin, IsTenantAdmin, IsCompanyAdmin + from gvsdsdk.models import Company as SdkCompany + from gvsdsdk.model_utils import UUIDObj as _U + qs = SdkFileAsset.objects.all() + user = getattr(self.request, 'user', None) + if IsSuperAdmin(request=self.request, user=user): + return qs + if IsTenantAdmin(request=self.request, user=user): + tenant_uuid = getattr(self.request, 'current_tenant_uuid', None) + if not tenant_uuid: + return qs.none() + tb = _U(tenant_uuid).bytes + companies = SdkCompany.objects.filter(TenantUUID=tb).values_list('CompanyUUID', flat=True) + company_bytes = [c.bytes if hasattr(c, 'bytes') else c for c in companies] + return qs.filter(CompanyUUID__in=company_bytes) + if IsCompanyAdmin(request=self.request, user=user): + company_uuid = getattr(self.request, 'current_company_uuid', None) + if not company_uuid: + return qs.none() + return qs.filter(CompanyUUID=_U(company_uuid).bytes) + raise PermissionDenied('需要公司管理员或租户管理员权限') + def list(self, request, *args, **kwargs): page_num = int(request.query_params.get('page', 1)) page_size = int(request.query_params.get('pageSize', 24))