Repository navigation
refactor: update workspace manage role usage in permissions #7838
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| from types import SimpleNamespace | ||
| from unittest.mock import patch | ||
|
|
||
| from django.test import SimpleTestCase | ||
|
|
||
| from common.auth.struct.auth import Auth | ||
| from common.exception.app_exception import AppUnauthorizedFailed | ||
| from users.views.user import WorkspaceUserListView | ||
|
|
||
|
|
||
| class WorkspaceUserListPermissionTests(SimpleTestCase): | ||
| def _request(self, roles): | ||
| return SimpleNamespace( | ||
| auth=Auth(roles=set(roles), permissions={}), | ||
| user=SimpleNamespace(id="user-1"), | ||
| query_params=SimpleNamespace(get=lambda *args, **kwargs: None), | ||
| ) | ||
|
|
||
| def test_workspace_scoped_manage_role_can_access_user_list(self): | ||
| request = self._request(["WORKSPACE_MANAGE:w:workspace-1"]) | ||
| with patch("users.views.user.UserManageSerializer.get_user_list", return_value=[]): | ||
| try: | ||
| result = WorkspaceUserListView().get(request, workspace_id="workspace-1") | ||
| except AppUnauthorizedFailed as exc: | ||
| self.fail(f"workspace user denied despite matching workspace role: {exc}") | ||
| self.assertEqual(result.status_code, 200) | ||
|
|
||
| def test_global_workspace_role_alone_cannot_access_workspace_user_list(self): | ||
| request = self._request(["WORKSPACE_MANAGE"]) | ||
| with patch("users.views.user.UserManageSerializer.get_user_list", return_value=[]): | ||
| with self.assertRaises(AppUnauthorizedFailed): | ||
| WorkspaceUserListView().get(request, workspace_id="workspace-1") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,7 +61,7 @@ class SystemUserGroupView(APIView): | |
| PermissionConstants.SYSTEM_USER_GROUP_CREATE, | ||
| PermissionConstants.SYSTEM_USER_GROUP_EDIT, | ||
| RoleConstants.ADMIN, | ||
| RoleConstants.WORKSPACE_MANAGE, | ||
| RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), | ||
| ) | ||
| @log( | ||
| menu="IAM/System User Group", | ||
|
|
@@ -87,7 +87,11 @@ def post(self, request: Request, workspace_id: str): | |
| responses=UserGroupListApi.get_response(), | ||
| tags=["IAM/System User Group"], | ||
| ) | ||
| @has_permissions(PermissionConstants.SYSTEM_USER_GROUP_READ, RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE) | ||
| @has_permissions( | ||
| PermissionConstants.SYSTEM_USER_GROUP_READ, | ||
| RoleConstants.ADMIN, | ||
| RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), | ||
| ) | ||
| def get(self, request: Request, workspace_id: str): | ||
| return result.success(SystemUserGroupCreateSerializer.Query(data={"workspace_id": workspace_id}).list()) | ||
|
|
||
|
|
@@ -104,7 +108,9 @@ class Delete(APIView): | |
| tags=["IAM/System User Group"], | ||
| ) | ||
| @has_permissions( | ||
| PermissionConstants.SYSTEM_USER_GROUP_DELETE, RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE | ||
| PermissionConstants.SYSTEM_USER_GROUP_DELETE, | ||
| RoleConstants.ADMIN, | ||
| RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), | ||
| ) | ||
| @log( | ||
| menu="IAM/System User Group", | ||
|
|
@@ -132,7 +138,9 @@ class AddMember(APIView): | |
| tags=["IAM/System User Group"], | ||
| ) | ||
| @has_permissions( | ||
| PermissionConstants.SYSTEM_USER_GROUP_ADD_MEMBER, RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE | ||
| PermissionConstants.SYSTEM_USER_GROUP_ADD_MEMBER, | ||
| RoleConstants.ADMIN, | ||
| RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), | ||
| ) | ||
| @log( | ||
| menu="IAM/System User Group", | ||
|
|
@@ -163,7 +171,9 @@ class RemoveMember(APIView): | |
| tags=["IAM/System User Group"], | ||
| ) | ||
| @has_permissions( | ||
| PermissionConstants.SYSTEM_USER_GROUP_REMOVE_MEMBER, RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE | ||
| PermissionConstants.SYSTEM_USER_GROUP_REMOVE_MEMBER, | ||
| RoleConstants.ADMIN, | ||
| RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), | ||
| ) | ||
| @log( | ||
| menu="IAM/System User Group", | ||
|
|
@@ -193,7 +203,9 @@ class UserList(APIView): | |
| responses=SystemUserGroupUserListPageResponse, | ||
| ) | ||
| @has_permissions( | ||
| PermissionConstants.SYSTEM_USER_GROUP_READ, RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE | ||
| PermissionConstants.SYSTEM_USER_GROUP_READ, | ||
| RoleConstants.ADMIN, | ||
| RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), | ||
| ) | ||
| def get(self, request: Request, workspace_id: str, user_group_id: str, current_page: int, page_size: int): | ||
| d = UserGroupListPageSerializer.Query( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Review of the diffSummaryThe change replaces Observations / Potential Issues
Minor nits
OverallThe patch is consistent within this file and appears safe provided |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -180,12 +180,12 @@ class WorkspaceUserListView(APIView): | |
| responses=WorkspaceUserAPI.get_response(), | ||
| ) | ||
| @has_permissions( | ||
| RoleConstants.WORKSPACE_MANAGE, | ||
| RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), | ||
| RoleConstants.ADMIN, | ||
| RoleConstants.EXTENDS_ADMIN, | ||
| RoleConstants.EXTENDS_WORKSPACE_MANAGE, | ||
| RoleConstants.USER, | ||
| RoleConstants.EXTENDS_USER, | ||
| RoleConstants.EXTENDS_WORKSPACE_MANAGE.get_workspace_role(), | ||
| RoleConstants.USER.get_workspace_role(), | ||
| RoleConstants.EXTENDS_USER.get_workspace_role(), | ||
| ) | ||
| def get(self, request: Request, workspace_id): | ||
| nick_name = request.query_params.get("nick_name", None) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's my review of the diff: Potential Issues1. Inconsistent transformation (likely a bug)Two entries are left untransformed:
But the similar "manage" and "user" roles got 2. Assumption that
|
||
|
|
||
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.
Here are the issues and suggestions for this test code:
Potential Issues
SimpleNamespaceused forquery_params— Only.get()is stubbed. IfWorkspaceUserListView.get()(or a serializer it calls) iterates, uses__getitem__,keys(), or checks truthiness ofquery_params, these tests will raiseAttributeError. Prefer a realdjango.http.QueryDict({}, mutable=False)orRequestFactory— it's free and matches production behavior.SimpleNamespacerequest instead of a real request object — Bypasses middleware,request.GET, and any attribute access order. If the view ever readsrequest.method,request.path, or DRF-style attributes, the test breaks for the wrong reason. Considerself.client.get(...)with an overriddenauth, orRequestFactory+ manualauthinjection.Authconstruction assumptions — Test assumesAuth(roles=set, permissions={})is a valid constructor and that roles are plain strings like"WORKSPACE_MANAGE:w:workspace-1". This couples the test to an internal string format; if role encoding changes (e.g., tuple format), all these tests silently mis-test. Verify against the actualAuthimplementation.Patching a specific call path (
users.views.user.UserManageSerializer.get_user_list) — brittle: refactors (renaming class, moving method, renaming imported symbol) break the test without changing behavior. Also note: patchingget_user_liston the class means theselfargument binding depends on how it's called (class vs instance). Ensure you're not accidentally patching a static/instance-method mismatch — aMagicMockassigned as a class attribute will receiveselfas first arg unless bound correctly, which can mask bugs.Assertion on
result.status_code == 200without asserting response content — if the view returns an error payload with status 200 (common in some frameworks returning{"code": ...}), the test passes erroneously. Assert on body/empty list too.except ... self.fail(...)pattern — fine, butself.failinsidetryafter catching will re-raiseAssertionError; ensureAppUnauthorizedFailedisn't a subclass ofExceptionhierarchy whereself.failtriggers further exception handling. PreferassertRaises/ positive assertion without try/except, or useself.assertNotRaiseshelper.Style / Minor
"workspace-1"repeated — define a constant to avoid typo drift between request setup and assert.workspace-2requestingworkspace-1should be denied. This is the key security boundary; currently untested.permissions={}empty — no test verifying that added permissions don't bypass scoping.SimpleTestCasemay fail at runtime if DB access occurs (serializer could hit DB); useTestCaseif there's any chance of DB queries.asyncconsideration — if the view supports async, tests should reflect the sync path being called intentionally.patch.object.Suggested Improvements (summary)
RequestFactory/QueryDictinstead ofSimpleNamespace.subTestto reduce duplication.