Skip to content

Commit

Permalink
Merge pull request #305 from zhu327/ft_template_update
Browse files Browse the repository at this point in the history
feat: template action list show delete tag
  • Loading branch information
zhu327 authored Nov 11, 2021
2 parents e954b12 + b3d0623 commit 230ee95
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 21 deletions.
2 changes: 1 addition & 1 deletion saas/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.11
1.5.12
32 changes: 31 additions & 1 deletion saas/backend/apps/template/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from backend.apps.policy.serializers import BasePolicyActionSLZ
from backend.apps.template.models import PermTemplate, PermTemplatePolicyAuthorized, PermTemplatePreUpdateLock
from backend.biz.policy import PolicyBean, PolicyBeanList
from backend.biz.role import RoleScopeSystemActions
from backend.biz.system import SystemBiz
from backend.service.constants import SubjectType

Expand Down Expand Up @@ -52,9 +53,12 @@ class TemplateListSLZ(serializers.ModelSerializer):
system = serializers.SerializerMethodField(label="系统信息")
tag = serializers.SerializerMethodField(label="标签")
is_lock = serializers.SerializerMethodField(label="是否锁定")
need_to_update = serializers.SerializerMethodField(label="是否需要更新")

def __init__(self, *args, **kwargs):
self.authorized_template = kwargs.pop("authorized_template", set())
self.role_system_actions: RoleScopeSystemActions = kwargs.pop("role_system_actions") # NOTE: 必须要传
assert self.role_system_actions
super().__init__(*args, **kwargs)
self._system_list = SystemBiz().new_system_list()

Expand All @@ -77,6 +81,7 @@ class Meta:
"subject_count",
"tag",
"is_lock",
"need_to_update",
"updater",
"updated_time",
"creator",
Expand All @@ -99,15 +104,40 @@ def get_tag(self, obj):
def get_is_lock(self, obj):
return obj.id in self._lock_ids

def get_need_to_update(self, obj):
# 如果系统不在授权范围内, 说明整个系统的操作都被删除了, 这个模板只能被删除
if not self.role_system_actions.has_system(obj.system_id):
return True

# 如果role的范围时任意, 模板不需要更新
if self.role_system_actions.is_all_action(obj.system_id):
return False

# template 的 action set 减去 role 的action set, 还有剩下的说明模板需要更新
rest_action = set(obj.action_ids) - set(self.role_system_actions.list_action_id(obj.system_id))
return len(rest_action) != 0


class TemplateListSchemaSLZ(serializers.ModelSerializer):
system = SystemInfoSLZ(label="系统信息", required=True)
tag = serializers.CharField(label="标签")
is_lock = serializers.BooleanField(label="是否锁定")
need_to_update = serializers.BooleanField(label="是否需要更新")

class Meta:
model = PermTemplate
fields = ("id", "system", "name", "description", "subject_count", "tag", "is_lock", "updater", "updated_time")
fields = (
"id",
"system",
"name",
"description",
"subject_count",
"tag",
"is_lock",
"need_to_update",
"updater",
"updated_time",
)


class TemplateRetrieveSchemaSLZ(TemplateListSchemaSLZ):
Expand Down
18 changes: 14 additions & 4 deletions saas/backend/apps/template/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,22 @@ def list(self, request, *args, **kwargs):
group_id = request.query_params.get("group_id", "")
queryset = self.filter_queryset(self.get_queryset())

# 查询role的system-actions set
role_system_actions = RoleListQuery(request.role).get_scope_system_actions()
page = self.paginate_queryset(queryset)
if page is not None:
# 查询模板中对group_id中有授权的
exists_template_set = self._query_group_exists_template_set(group_id, page)
serializer = TemplateListSLZ(page, many=True, authorized_template=exists_template_set)
serializer = TemplateListSLZ(
page, many=True, authorized_template=exists_template_set, role_system_actions=role_system_actions
)
return self.get_paginated_response(serializer.data)

# 查询模板中对group_id中有授权的
exists_template_set = self._query_group_exists_template_set(group_id, queryset)
serializer = TemplateListSLZ(queryset, many=True, authorized_template=exists_template_set)
serializer = TemplateListSLZ(
queryset, many=True, authorized_template=exists_template_set, role_system_actions=role_system_actions
)
return Response(serializer.data)

def _query_group_exists_template_set(self, group_id: str, queryset) -> Set[int]:
Expand Down Expand Up @@ -197,12 +203,16 @@ def retrieve(self, request, *args, **kwargs):
slz.is_valid(raise_exception=True)
grouping = slz.validated_data["grouping"]

# 查询role的system-actions set
role_system_actions = RoleListQuery(request.role).get_scope_system_actions()
template = get_object_or_404(self.queryset, pk=kwargs["id"])
serializer = TemplateListSLZ(instance=template)
serializer = TemplateListSLZ(instance=template, role_system_actions=role_system_actions)
data = serializer.data
template_action_set = set(template.action_ids)

actions = self.action_biz.list_checked_action_by_role(template.system_id, request.role, template_action_set)
actions = self.action_biz.list_template_tagged_action_by_role(
template.system_id, request.role, template_action_set
)
if grouping:
action_groups = self.action_group_biz.list_by_actions(template.system_id, actions)
data["actions"] = [one.dict() for one in action_groups]
Expand Down
36 changes: 30 additions & 6 deletions saas/backend/biz/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,38 @@ def list_by_role(self, system_id: str, role) -> List[ActionBean]:
actions = action_list.filter_by_scope_action_ids(scope_action_ids)
return actions

def list_checked_action_by_role(self, system_id: str, role, checked_action_set: Set[str]) -> List[ActionBean]:
def list_template_tagged_action_by_role(
self, system_id: str, role, template_action_set: Set[str]
) -> List[ActionBean]:
"""
查询角色相关的操作列表, 并上标签
查询角色相关的操作列表, 加上标签
返回的操作是role范围+template操作的合集
对role范围外的操作要打上delete标签
"""
actions = self.list_by_role(system_id, role)
for action in actions:
action.tag = ActionTag.CHECKED.value if action.id in checked_action_set else ActionTag.UNCHECKED.value
return actions
actions = self.list(system_id).actions
scope_action_ids = RoleListQuery(role).list_scope_action_id(system_id)
if ACTION_ALL in scope_action_ids:
for action in actions:
action.tag = ActionTag.CHECKED.value if action.id in template_action_set else ActionTag.UNCHECKED.value
return actions

# 筛选出在role范围内+在模板操作的操作集合
# 存量模板的action范围可能会超过role的范围, 为了展示完整, 需要补全role范围中没有的action
filter_actions = [
action for action in actions if action.id in scope_action_ids or action.id in template_action_set
]
for action in filter_actions:
# 如果操作在模板内且不在role范围内, 操作已被删除
if action.id in template_action_set and action.id not in scope_action_ids:
action.tag = ActionTag.DELETE.value
# 如果操作同时在role范围内与模板内, 操作已勾选
elif action.id in template_action_set and action.id in scope_action_ids:
action.tag = ActionTag.CHECKED.value
# 如果操作不在模板内, 操作未勾选
else:
action.tag = ActionTag.UNCHECKED.value
return filter_actions

def list_by_subject(self, system_id: str, role, subject: Subject) -> List[ActionBean]:
"""
Expand Down
1 change: 1 addition & 0 deletions saas/backend/biz/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ class ActionTag(LowerStrEnum):
READONLY = auto()
CHECKED = auto()
UNCHECKED = auto()
DELETE = auto()
51 changes: 45 additions & 6 deletions saas/backend/biz/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"""
import logging
from collections import defaultdict
from typing import List, Optional
from typing import Dict, List, Optional, Set

from django.db.models import Q
from django.utils.functional import cached_property
Expand Down Expand Up @@ -60,6 +60,38 @@ class AuthScopeSystemBean(BaseModel):
actions: List[PolicyBean]


class RoleScopeSystemActions(BaseModel):
"""
role的授权范围系统-操作列表
"""

systems: Dict[str, Set[str]] # key: system_id, value: action_id_set

def is_all_action(self, system_id: str) -> bool:
"""
是否是全操作列表
"""
if not self.has_system(system_id):
return False

if SYSTEM_ALL in self.systems or ACTION_ALL in self.systems[system_id]:
return True

return False

def has_system(self, system_id: str) -> bool:
return SYSTEM_ALL in self.systems or system_id in self.systems

def list_action_id(self, system_id: str) -> List[str]:
"""
返回role范围内的所有action_id, 如果为all_action, 应该事先判断
"""
if system_id in self.systems:
return list(self.systems[system_id])

return []


class RoleBiz:
svc = RoleService()
system_svc = SystemService()
Expand Down Expand Up @@ -280,15 +312,22 @@ def list_scope_action_id(self, system_id: str) -> List[str]:
if self.role.type == RoleType.STAFF.value:
return [ACTION_ALL]

scopes = self.role_svc.list_auth_scope(self.role.id)
systems = {s.system_id: {a.id for a in s.actions} for s in scopes}
if system_id not in systems and SYSTEM_ALL not in systems:
system_actions = self.get_scope_system_actions()
if not system_actions.has_system(system_id):
return []

if SYSTEM_ALL in systems or ACTION_ALL in systems[system_id]:
if system_actions.is_all_action(system_id):
return [ACTION_ALL]

return list(systems[system_id])
return system_actions.list_action_id(system_id)

def get_scope_system_actions(self) -> RoleScopeSystemActions:
"""
获取授权范围的系统-操作字典
"""
scopes = self.role_svc.list_auth_scope(self.role.id)
systems = {s.system_id: {a.id for a in s.actions} for s in scopes}
return RoleScopeSystemActions(systems=systems)

def query_template(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion saas/backend/common/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def _clean(self, data: Dict[str, Any]):
self._clean(one)
elif isinstance(value, str):
for sk in self.sensitive_keys:
if key.endswith(sk):
if str(key).endswith(sk):
data[key] = "***"

if key in self.sensitive_key_func:
Expand Down
2 changes: 1 addition & 1 deletion saas/backend/common/vue.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def get(self, request):
# csrftoken name
"CSRF_COOKIE_NAME": settings.CSRF_COOKIE_NAME,
# BK_ITSM
"BK_ITSM_APP_URL": settings.BK_ITSM_APP_URL,
"BK_ITSM_APP_URL": settings.BK_ITSM_APP_URL.rstrip("/"),
}

# 添加前端功能启用开关
Expand Down
6 changes: 6 additions & 0 deletions saas/resources/version_log/V1.5.12_2021-11-11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# V1.5.12 版本更新日志

### 功能优化
* 增加用户同步记录
* 分级管理员修改操作范围后, 范围不一致的模板不能授权

5 changes: 5 additions & 0 deletions saas/resources/version_log/V1.5.12_2021-11-11_en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# V1.5.12 ChangeLog

### Optimization Updates
* Add user synchronization records
* After the rating manager modifies the action scope, templates with inconsistent scopes cannot be authorized
72 changes: 71 additions & 1 deletion saas/tests/biz/role_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
from unittest import mock

import pytest
from django.test import TestCase

from backend.apps.role.models import Role
from backend.biz.policy import InstanceBean
from backend.biz.role import ActionScopeDiffer
from backend.biz.role import ActionScopeDiffer, RoleListQuery, RoleScopeSystemActions
from backend.service.constants import ACTION_ALL, SYSTEM_ALL, RoleType


class TestInstanceDiff(TestCase):
Expand Down Expand Up @@ -60,3 +65,68 @@ def test_false(self):
]

self.assertFalse(ActionScopeDiffer(None, None)._diff_instances(template_instances, scope_instances))


@pytest.fixture()
def role_scope_system_action_system_all():
return RoleScopeSystemActions(systems={SYSTEM_ALL: set()})


@pytest.fixture()
def role_scope_system_action_action_all():
return RoleScopeSystemActions(systems={"system": {ACTION_ALL}})


@pytest.fixture()
def role_scope_system_action_normal():
return RoleScopeSystemActions(systems={"system": {"action"}})


class TestRoleScopeSystemActions:
def test_has_system(self, role_scope_system_action_system_all, role_scope_system_action_action_all):
assert role_scope_system_action_system_all.has_system("system")

assert role_scope_system_action_action_all.has_system("system")
assert not role_scope_system_action_action_all.has_system("test")

def test_is_all_action(
self, role_scope_system_action_system_all, role_scope_system_action_action_all, role_scope_system_action_normal
):
assert role_scope_system_action_system_all.is_all_action("system")

assert role_scope_system_action_action_all.is_all_action("system")
assert not role_scope_system_action_action_all.is_all_action("test")

assert not role_scope_system_action_normal.is_all_action("system")

def test_list_action_id(
self, role_scope_system_action_system_all, role_scope_system_action_action_all, role_scope_system_action_normal
):
assert role_scope_system_action_system_all.list_action_id("system") == []

assert role_scope_system_action_action_all.list_action_id("system") == [ACTION_ALL]
assert role_scope_system_action_action_all.list_action_id("test") == []

assert role_scope_system_action_normal.list_action_id("system") == ["action"]


class TestRoleListQuery:
def test_list_scope_action_id(self, role_scope_system_action_system_all, role_scope_system_action_normal):
role = Role(type=RoleType.STAFF.value)
q = RoleListQuery(role=role)
assert q.list_scope_action_id("system") == [ACTION_ALL]

role = Role(type=RoleType.RATING_MANAGER.value)
q = RoleListQuery(role=role)
q.get_scope_system_actions = mock.Mock(return_value=role_scope_system_action_normal)
assert q.list_scope_action_id("test") == []

role = Role(type=RoleType.RATING_MANAGER.value)
q = RoleListQuery(role=role)
q.get_scope_system_actions = mock.Mock(return_value=role_scope_system_action_system_all)
assert q.list_scope_action_id("system") == [ACTION_ALL]

role = Role(type=RoleType.RATING_MANAGER.value)
q = RoleListQuery(role=role)
q.get_scope_system_actions = mock.Mock(return_value=role_scope_system_action_normal)
assert q.list_scope_action_id("system") == ["action"]

0 comments on commit 230ee95

Please sign in to comment.