-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat: Folder authorization backend #4185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
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. DetailsInstructions 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. |
|
[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. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
| RoleConstants.WORKSPACE_MANAGE.get_workspace_role() | ||
| ) | ||
| @log( | ||
| menu='folder', operate='Delete folder', |
There was a problem hiding this comment.
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:
-
Constant Duplication: You're importing
PermissionConstantstwice at the top. Removing this duplication will make the file cleaner. -
Incorrect Import Statement: The import statement for
CommonConstantsis incorrect; it should beRoleConstants.Role. -
Resource Path Calculation: In the
post,edit, anddeletemethods, the resource path uses hardcoded strings like/WORKSPACE/<workspace_id>/<source>. This might not cover all use cases ifsourcevalues vary significantly. -
Method Redundancy: There's no need for the
Operateinner 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 functionsKey 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], |
There was a problem hiding this comment.
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:
- Consistency: The use of
valuefrom the Enum can make some parts more readable and concise.
class Group(Enum):
OVERVIEW = "OVERVIEW"
OPERATION_LOG = "OPERATION_LOG"
class SystemGroup(Enum):
...- Resource Permissions:
- You have redundant assignments like
KNOWLEDGEtwice. Ensure you only assign once if it's correct to do so in each context (e.g., knowledge vs. knowledge folder).
- You have redundant assignments like
class Resource(models.TextChoices):
KNOWLEDGE_FOLDER = (
Group.KNOWLEDGE_FOLDER.value,
"KNOWLEDGE_FOLDER",
)
...
class ResourcePermissionConst:
KNOWLEDGE_MANGE = ResourcePermissionGroup(
resource=Resource.KNOWLEDGE_FOLDER,
permission=ResourcePermission.MANAGE,
)
... - 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.
- 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
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...- 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.
| nodes = Folder.objects.filter(base_q).get_cached_trees() | ||
|
|
||
| TreeSerializer = get_folder_tree_serializer(self.data.get('source')) # noqa | ||
| serializer = TreeSerializer(nodes, many=True) |
There was a problem hiding this comment.
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
-
Inconsistent Import Statements: The
FuncandFimports fromdjango.db.modelsshould appear before other related imports (QuerySet, etc.), which improves readability. -
Redundant Condition Check: Inside
_check_tree_integrity, there's redundant checking to returnTrue. -
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. -
Unnecessary User Permissions Check: If permissions are already checked during filtering, they should ideally not be re-checked separately.
-
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. -
Variable Naming: The variable
base_qis used in multiple places, but it doesn't clearly describe its purpose. Consider renaming it or adding comments explaining its usage.
Optimization Suggestions
-
Combine Logical Conditions: Merge logical AND/OR operations within a single clause for better performance.
-
Use Query Sets Efficiently: Combine all filters into one queryset to reduce overhead and improve efficiency by using fewer database queries.
-
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.
feat: Folder authorization backend