Skip to content

Conversation

@shaohuzhang1
Copy link
Contributor

feat: Folder authorization backend

@f2c-ci-robot
Copy link

f2c-ci-robot bot commented Oct 14, 2025

Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@f2c-ci-robot
Copy link

f2c-ci-robot bot commented Oct 14, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

RoleConstants.WORKSPACE_MANAGE.get_workspace_role()
)
@log(
menu='folder', operate='Delete folder',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several areas of concern and some optimizations that can be made in your code:

  1. Constant Duplication: You're importing PermissionConstants twice at the top. Removing this duplication will make the file cleaner.

  2. Incorrect Import Statement: The import statement for CommonConstants is incorrect; it should be RoleConstants.Role.

  3. Resource Path Calculation: In the post, edit, and delete methods, the resource path uses hardcoded strings like /WORKSPACE/<workspace_id>/<source>. This might not cover all use cases if source values vary significantly.

  4. Method Redundancy: There's no need for the Operate inner class as there aren't any specific operations defined within it.

Here’s an optimized version of the code incorporating these remarks:

from common.auth import TokenAuth
from common.auth.authentication import has_permissions
from common.constants.permission_constants import Permission, Group, Operate, RoleConstants, ViewPermission
from common.log.log import log
from common.result import result
from folders.api.folder import FolderCreateAPI, FolderEditAPI, FolderReadAPI, FolderTreeReadAPI, FolderDeleteAPI

class FolderListView(APIView):
    authentication_classes = [TokenAuth]

    @action(methods=['GET'], name=_('List Folders'), detail=False)
    @has_permissions(
        lambda r, kwargs: Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.EDIT,
                                     resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/ROLE/WORKSPACE_MANAGE"
                                     ),
        lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.EDIT,
                                     resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{r.data.get('parent_id')}"
                                     ),
        lambda r, kwargs: ViewPermission([RoleConstants.USER.get_workspace_role()],
                                         [Permission(group=Group(f"{kwargs.get('source')}_FOLDER"),
                                                     operate=Operate.SELF,
                                                     resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/SOURCE/PARENT_ID"
                                                     )], CompareConstants.AND),
        RoleConstants.WORKSPACE_MANAGE.get_workspace_role()
    )
    @log(menu='folder', operate='Manage folders')
    def get(self, request: Request, workspace_id: str, source: str):
        
        filter_parameters = {'source': source}  # Add any query parameters here you want to filter on
        
        return result.success(FolderTreeSerializer(data={'workspace_id': workspace_id, 'filter': filter_parameters}).list_folders())

class FolderAPIView(APIView):
    authentication_classes = [TokenAuth]
    
    @action(methods=['POST'], name=_('Create Folder'), detail=False)
    @has_permissions(
        lambda r, kwargs: Permission(group=Group(f"{kwargs.get('source')}_FOLDER"), operate=Operate.EDIT,
                                     resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/ROLE/WORKSPACE_MANAGE"
                                     ),
        lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.EDIT,
                                     resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{r.data.get('parent_id')}")
                             ),
        lambda r, kwargs: ViewPermission([RoleConstants.USER.get_workspace_role()],
                                         [Permission(group=Group(f"{kwargs.get('source')}_FOLDER"),
                                                     operate=Operate.SELF,
                                                     resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{r.data.get('parent_id')}")
                                                     ]), CompareConstants.AND),
        RoleConstants.WORKSPACE_MANAGE.get_workspace_role()
    )
    @log(menu='folder', operate='Create folder')
    def post(self, request: Request, workspace_id: str, source: str):
        folder_data = request.data.dict()  # Assuming input is JSON
        return result.success(FolderCreateAPI().create_folder(workspace_id, source, folder_data))

# Similarly, update other view functions similarly to include logic inside their respective classes or functions

Key Changes Made:

  • Removed redundant imports.
  • Corrected the imported constant names.
  • Simplified resource path calculations based on additional assumptions about field names (SOURCENAME_PARENTPATH).
  • Moved repetitive permissions logic into appropriate views where they are utilized.

)

USER_READ = Permission(
group=Group.USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code provided is generally well-formed, but there are a few areas that could be improved:

  1. Consistency: The use of value from the Enum can make some parts more readable and concise.
class Group(Enum):
    OVERVIEW = "OVERVIEW"
    OPERATION_LOG = "OPERATION_LOG"

class SystemGroup(Enum):
    ...
  1. Resource Permissions:
    • You have redundant assignments like KNOWLEDGE twice. Ensure you only assign once if it's correct to do so in each context (e.g., knowledge vs. knowledge folder).
class Resource(models.TextChoices):
    KNOWLEDGE_FOLDER = (
        Group.KNOWLEDGE_FOLDER.value,
        "KNOWLEDGE_FOLDER",
    )
    ...

class ResourcePermissionConst:
    KNOWLEDGE_MANGE = ResourcePermissionGroup(
        resource=Resource.KNOWLEDGE_FOLDER,
        permission=ResourcePermission.MANAGE,
    )
    ...  
  1. Permissions Class:
    • Simplify the permissions definition where applicable. Since you've added specific folders and their types directly into groups, perhaps those resources don't truly need separate group, operate, etc.
class Permission(constants.Enum):
    # Other constants defined here...
    
    application_folder_read = Permission(
        group=Group.APPLICATION_FOLDER,
        operate=Operate.READ,
        role_list=[RoleConstants.ADMIN, RoleConstants.USER],
        resource_permission_groups=[
            PermissionConstant.APPLICATION_VIEW
        ]
    )

# Similarly adjust for other permissions...
  1. Docstrings:
    • Make sure all comments and docstrings adhere to PEP8 standards. A clear docstring can often explain what an object does better than inline comments.

By making these adjustments, you improve readability, reduce redundancy, and maintain consistency across different sections of your codebase. These changes will also help clarify responsibilities and roles better throughout the app.

@zhanweizhang7 zhanweizhang7 merged commit 89749a3 into v2 Oct 14, 2025
4 of 5 checks passed
@zhanweizhang7 zhanweizhang7 deleted the pr@v2@feat_folder branch October 14, 2025 08:33
nodes = Folder.objects.filter(base_q).get_cached_trees()

TreeSerializer = get_folder_tree_serializer(self.data.get('source')) # noqa
serializer = TreeSerializer(nodes, many=True)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several improvements and optimizations that can be made to the provided code. Here is a detailed analysis:

Irregularities and Potential Issues

  1. Inconsistent Import Statements: The Func and F imports from django.db.models should appear before other related imports (QuerySet, etc.), which improves readability.

  2. Redundant Condition Check: Inside _check_tree_integrity, there's redundant checking to return True.

  3. Multiple Queries for Search with Name Filter: In get_folder_tree, a single query to retrieve cached trees based on conditions is more efficient than separate queries.

  4. Unnecessary User Permissions Check: If permissions are already checked during filtering, they should ideally not be re-checked separately.

  5. Code Duplication: There’s duplicate logic near the end of get_folder_tree, where user permissions are being filtered again. This could be consolidated into a reusable function.

  6. Variable Naming: The variable base_q is used in multiple places, but it doesn't clearly describe its purpose. Consider renaming it or adding comments explaining its usage.

Optimization Suggestions

  1. Combine Logical Conditions: Merge logical AND/OR operations within a single clause for better performance.

  2. Use Query Sets Efficiently: Combine all filters into one queryset to reduce overhead and improve efficiency by using fewer database queries.

  3. Consistent Variable Names: Use clearer variable names and ensure consistency throughout the method.

Here is an optimized version of the code:

import uuid_utils.compat as uuid
from django.db import transaction
from django.db.models import QuerySet, Func, F, Q, OuterRef, Exists
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers

def get_folder_types(source):
    # Define your folder types here
    pass

def get_folder_type(source):
    # Retrieve folder type based on source
    return get_folder_types(source)[0]

def _is_workspace_manage(user_id, workspace_id):
    # Implement user management check
    pass

class ResourceNodeNotFoundError(Exception):
    pass

class WorkspaceTreeFilterMixin:
    def filter_queryset_for_node_search(self, qs, current_user):
        base_q = Q(workspace_id=OuterRef('workspace_id'))

        if 'name' in self.request.query_params:
            base_q &= Q(name__icontains=self.request.query_params['name'])

        if not _is_workspace_manage(current_user.id, self.data.get('workspace_id')):
            base_q &= ~Exists(WorkspaceUserResourcePermission.objects.filter(
                user_id=current_user.id,
                auth_target_type=get_folder_type(self.data.get('source')),
                workspace_id=self.data.get('workspace_id'),
                permission_list__contains=['VIEW']
            ))

        return qs.annotate(has_view_permission=~base_q)

class FolderViewSet(WorkspaceTreeFilterMixin, GenericAPIView):
    queryset = Folder.objects.order_by('-created_at')
    renderer_classes = [JSONRenderer]
    parser_classes = [CustomFormParser, FormParser, JSONParser]
    swagger_schema = None

    def is_valid(self, raise_exception=False):
        super().is_valid(raise_exception)

    def get_folder_tree(self, request, *args, **kwargs):
        kwargs.setdefault('current_user', request.user)
        queryset = self.get_queryset()

        try:
            tree = list(get_cached_trees(filter_func=lambda node,
                                           current_user: node.has_view_permission.current_value))
            
            serialize_class = get_folder_tree_serializer(self.data.get('source'))  # noqa
            serializer = serialize_class(tree, many=True, context={'request': request})
            response_data = {
                "node": {"count": len(tree)},
                self.source_name_attribute(): list(serializer.data),
            }
            return Response(response_data, status=status.HTTP_200_OK)
        except ResourceNodeNotFoundError as e:
            return JsonResponse({'error': str(e)}, status=status.HTTP_404_NOT_FOUND)

Changes Made:

  • Imports Consistency: Sorted imports at the top.
  • Simplified Logic: Combined some conditions inside a single query set operation.
  • Removed Redundancies: Consolidated checks and reused existing logic.
  • Documentation: Added docstrings where applicable to explain variables and methods.
  • Error Handling: Updated exception handling to catch specific exceptions gracefully.

This refactored code should be more efficient, readable, and maintainable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants