Skip to content
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

feat: added audit operation for data source and user #1981

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 37 additions & 30 deletions src/bk-user/bkuser/apis/web/data_source/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
)
from bkuser.apis.web.mixins import CurrentUserTenantMixin
from bkuser.apps.audit.constants import ObjectTypeEnum, OperationEnum
from bkuser.apps.audit.recorder import add_audit_record
from bkuser.apps.audit.data_model import AuditObject
from bkuser.apps.audit.recorder import add_audit_record, batch_add_audit_records
from bkuser.apps.data_source.constants import DataSourceTypeEnum
from bkuser.apps.data_source.models import (
DataSource,
Expand Down Expand Up @@ -80,6 +81,7 @@
from bkuser.idp_plugins.constants import BuiltinIdpPluginEnum
from bkuser.plugins.base import get_default_plugin_cfg, get_plugin_cfg_schema_map, get_plugin_cls
from bkuser.plugins.constants import DataSourcePluginEnum
from bkuser.utils.django import get_model_dict

from .schema import get_data_source_plugin_cfg_json_schema

Expand Down Expand Up @@ -193,11 +195,9 @@ def post(self, request, *args, **kwargs):
operation=OperationEnum.CREATE_DATA_SOURCE,
object_type=ObjectTypeEnum.DATA_SOURCE,
object_id=ds.id,
extras={
"plugin_config": ds.plugin_config,
"field_mapping": ds.field_mapping,
"sync_config": ds.sync_config,
},
data_before={},
data_after=get_model_dict(ds),
extras={},
)

return Response(
Expand Down Expand Up @@ -253,11 +253,7 @@ def put(self, request, *args, **kwargs):
data = slz.validated_data

# 【审计】记录变更前数据
data_before = {
"plugin_config": data_source.plugin_config,
"field_mapping": data_source.field_mapping,
"sync_config": data_source.sync_config,
}
data_before = get_model_dict(data_source)

with transaction.atomic():
data_source.field_mapping = data["field_mapping"]
Expand All @@ -274,7 +270,9 @@ def put(self, request, *args, **kwargs):
operation=OperationEnum.MODIFY_DATA_SOURCE,
object_type=ObjectTypeEnum.DATA_SOURCE,
object_id=data_source.id,
extras={"data_before": data_before},
data_before=data_before,
data_after=get_model_dict(data_source),
extras={},
)

return Response(status=status.HTTP_204_NO_CONTENT)
Expand Down Expand Up @@ -308,14 +306,27 @@ def delete(self, request, *args, **kwargs):
# 待删除的认证源
waiting_delete_idps = Idp.objects.filter(**idp_filters)

# 【审计】记录变更前数据,数据删除后便无法获取
idps_before_delete = list(
waiting_delete_idps.values("id", "name", "status", "plugin_config", "data_source_match_rules")
# 记录 data_source 删除前数据
data_source_audit_object = AuditObject(
id=data_source.id,
type=ObjectTypeEnum.DATA_SOURCE,
operation=OperationEnum.RESET_DATA_SOURCE,
data_before=get_model_dict(data_source),
data_after={},
extras={},
)
data_source_id = data_source.id
plugin_config = data_source.plugin_config
field_mapping = data_source.field_mapping
sync_config = data_source.sync_config
# 记录 idp 删除前数据
idp_audit_objects = [
AuditObject(
id=idp.id,
type=ObjectTypeEnum.IDP,
operation=OperationEnum.RESET_IDP,
data_before=get_model_dict(idp),
data_after={},
extras={},
)
for idp in list(waiting_delete_idps)
]

with transaction.atomic():
# 删除认证源敏感信息
Expand All @@ -334,20 +345,12 @@ def delete(self, request, *args, **kwargs):
# 删除数据源 & 关联资源数据
DataSourceHandler.delete_data_source_and_related_resources(data_source)

audit_objects = [data_source_audit_object] + idp_audit_objects
# 审计记录
add_audit_record(
batch_add_audit_records(
operator=request.user.username,
tenant_id=self.get_current_tenant_id(),
operation=OperationEnum.DELETE_DATA_SOURCE,
object_type=ObjectTypeEnum.DATA_SOURCE,
object_id=data_source_id,
extras={
"is_delete_idp": is_delete_idp,
"plugin_config": plugin_config,
"field_mapping": field_mapping,
"sync_config": sync_config,
"idps_before_delete": idps_before_delete,
},
objects=audit_objects,
)

return Response(status=status.HTTP_204_NO_CONTENT)
Expand Down Expand Up @@ -550,6 +553,8 @@ def post(self, request, *args, **kwargs):
operation=OperationEnum.SYNC_DATA_SOURCE,
object_type=ObjectTypeEnum.DATA_SOURCE,
object_id=data_source.id,
data_before={},
data_after={},
extras={"overwrite": options.overwrite, "incremental": options.incremental, "trigger": options.trigger},
)

Expand Down Expand Up @@ -603,6 +608,8 @@ def post(self, request, *args, **kwargs):
operation=OperationEnum.SYNC_DATA_SOURCE,
object_type=ObjectTypeEnum.DATA_SOURCE,
object_id=data_source.id,
data_before={},
Copy link
Collaborator

Choose a reason for hiding this comment

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

建议 data_before, data_after, extras 都做成可选参数

data_after={},
extras={"overwrite": options.overwrite, "incremental": options.incremental, "trigger": options.trigger},
)

Expand Down
39 changes: 38 additions & 1 deletion src/bk-user/bkuser/apis/web/organization/views/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
#
# We undertake not to change the open source license (MIT license) applicable
# to the current version of the project delivered to anyone in the future.
from collections import defaultdict
from typing import Dict, List

from django.utils.translation import gettext_lazy as _

from bkuser.apis.web.mixins import CurrentUserTenantMixin
from bkuser.apps.data_source.constants import DataSourceTypeEnum
from bkuser.apps.data_source.models import DataSource
from bkuser.apps.data_source.models import DataSource, DataSourceDepartmentUserRelation, DataSourceUserLeaderRelation
from bkuser.common.error_codes import error_codes


Expand All @@ -40,3 +43,37 @@ def get_current_tenant_local_real_data_source(self) -> DataSource:
raise error_codes.DATA_SOURCE_NOT_EXIST.f(_("当前租户不存在本地实名用户数据源"))

return real_data_source


class CurrentUserDepartmentRelationMixin:
"""获取用户与部门之间的映射关系"""
Copy link
Collaborator

Choose a reason for hiding this comment

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

需要讨论:加这个 mixin 来算数据,是否合适?


def get_user_department_map(self, data_source_user_ids: List[int]) -> Dict:
# 记录用户与部门之间的映射关系
user_department_relations = DataSourceDepartmentUserRelation.objects.filter(
user_id__in=data_source_user_ids
).values("department_id", "user_id")
user_department_map = defaultdict(list)

# 将用户的所有部门存储在列表中
for relation in user_department_relations:
user_department_map[relation["user_id"]].append(relation["department_id"])

return user_department_map


class CurrentUserLeaderRelationMixin:
"""获取用户与上级之间的映射关系"""

def get_user_leader_map(self, data_source_user_ids: List[int]) -> Dict:
# 记录用户与上级之间的映射关系
user_leader_relations = DataSourceUserLeaderRelation.objects.filter(user_id__in=data_source_user_ids).values(
"leader_id", "user_id"
)
user_leader_map = defaultdict(list)

# 将用户的所有上级存储在列表中
for relation in user_leader_relations:
user_leader_map[relation["user_id"]].append(relation["leader_id"])

return user_leader_map
140 changes: 136 additions & 4 deletions src/bk-user/bkuser/apis/web/organization/views/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,22 @@
TenantDeptUserRelationBatchPatchInputSLZ,
TenantDeptUserRelationBatchUpdateInputSLZ,
)
from bkuser.apis.web.organization.views.mixins import CurrentUserTenantDataSourceMixin
from bkuser.apis.web.organization.views.mixins import (
CurrentUserDepartmentRelationMixin,
CurrentUserTenantDataSourceMixin,
)
from bkuser.apps.audit.constants import ObjectTypeEnum, OperationEnum
from bkuser.apps.audit.data_model import AuditObject
from bkuser.apps.audit.recorder import batch_add_audit_records
from bkuser.apps.data_source.models import DataSourceDepartmentUserRelation
from bkuser.apps.permission.constants import PermAction
from bkuser.apps.permission.permissions import perm_class
from bkuser.apps.tenant.models import TenantDepartment, TenantUser


class TenantDeptUserRelationBatchCreateApi(CurrentUserTenantDataSourceMixin, generics.CreateAPIView):
class TenantDeptUserRelationBatchCreateApi(
CurrentUserTenantDataSourceMixin, CurrentUserDepartmentRelationMixin, generics.CreateAPIView
):
"""批量添加 / 拉取租户用户(添加部门 - 用户关系)"""

permission_classes = [IsAuthenticated, perm_class(PermAction.MANAGE_TENANT)]
Expand Down Expand Up @@ -66,6 +74,9 @@ def post(self, request, *args, **kwargs):
id__in=data["user_ids"],
).values_list("data_source_user_id", flat=True)

# 【审计】记录变更前用户-部门映射
user_department_map_before = self.get_user_department_map(data_source_user_ids=data_source_user_ids)

# 复制操作:为数据源部门 & 用户添加关联边,但是不会影响存量的关联边
relations = [
DataSourceDepartmentUserRelation(user_id=user_id, department_id=dept_id, data_source=data_source)
Expand All @@ -74,10 +85,39 @@ def post(self, request, *args, **kwargs):
# 由于复制操作不会影响存量的关联边,所以需要忽略冲突,避免出现用户复选的情况
DataSourceDepartmentUserRelation.objects.bulk_create(relations, ignore_conflicts=True)

# 【审计】记录变更后用户-部门映射
user_department_map = self.get_user_department_map(data_source_user_ids=data_source_user_ids)

# 【审计】创建用户-部门审计对象
audit_objects = [
AuditObject(
id=tenant_user.data_source_user_id,
name=tenant_user.data_source_user.username,
type=ObjectTypeEnum.DATA_SOURCE_USER,
operation=OperationEnum.MODIFY_USER_DEPARTMENT_RELATIONS,
data_before={"department_ids": user_department_map_before[tenant_user.data_source_user_id]},
data_after={"department_ids": user_department_map[tenant_user.data_source_user_id]},
extras={"department_ids": list(data_source_dept_ids)},
)
for tenant_user in TenantUser.objects.filter(
tenant_id=cur_tenant_id,
id__in=data["user_ids"],
).select_related("data_source_user")
]

# 【审计】批量添加审计记录
batch_add_audit_records(
operator=request.user.username,
tenant_id=cur_tenant_id,
objects=audit_objects,
)

return Response(status=status.HTTP_204_NO_CONTENT)


class TenantDeptUserRelationBatchUpdateApi(CurrentUserTenantDataSourceMixin, generics.UpdateAPIView):
class TenantDeptUserRelationBatchUpdateApi(
CurrentUserTenantDataSourceMixin, CurrentUserDepartmentRelationMixin, generics.UpdateAPIView
):
"""批量移动租户用户(更新部门 - 用户关系)"""

permission_classes = [IsAuthenticated, perm_class(PermAction.MANAGE_TENANT)]
Expand Down Expand Up @@ -107,6 +147,9 @@ def put(self, request, *args, **kwargs):
id__in=data["user_ids"],
).values_list("data_source_user_id", flat=True)

# 【审计】记录变更前用户-部门映射
user_department_map_before = self.get_user_department_map(data_source_user_ids=data_source_user_ids)

# 移动操作:为数据源部门 & 用户添加关联边,但是会删除这批用户所有的存量关联边
with transaction.atomic():
# 先删除
Expand All @@ -118,6 +161,33 @@ def put(self, request, *args, **kwargs):
]
DataSourceDepartmentUserRelation.objects.bulk_create(relations)

# 【审计】记录变更后用户-部门映射
user_department_map = self.get_user_department_map(data_source_user_ids=data_source_user_ids)

# 【审计】创建用户-部门审计对象
audit_objects = [
AuditObject(
id=tenant_user.data_source_user_id,
name=tenant_user.data_source_user.username,
type=ObjectTypeEnum.DATA_SOURCE_USER,
operation=OperationEnum.MODIFY_USER_DEPARTMENT_RELATIONS,
data_before={"department_ids": user_department_map_before[tenant_user.data_source_user_id]},
data_after={"department_ids": user_department_map[tenant_user.data_source_user_id]},
extras={"department_ids": list(data_source_dept_ids)},
)
for tenant_user in TenantUser.objects.filter(
tenant_id=cur_tenant_id,
id__in=data["user_ids"],
).select_related("data_source_user")
]

# 【审计】批量添加审计记录
batch_add_audit_records(
operator=request.user.username,
tenant_id=cur_tenant_id,
objects=audit_objects,
)

return Response(status=status.HTTP_204_NO_CONTENT)

@swagger_auto_schema(
Expand Down Expand Up @@ -147,6 +217,9 @@ def patch(self, request, *args, **kwargs):
id__in=data["user_ids"],
).values_list("data_source_user_id", flat=True)

# 【审计】记录变更前用户-部门映射
user_department_map_before = self.get_user_department_map(data_source_user_ids=data_source_user_ids)

# 移动操作:为数据源部门 & 用户添加关联边,但是会删除这批用户在当前部门的存量关联边
with transaction.atomic():
# 先删除(仅限于指定部门)
Expand All @@ -160,10 +233,39 @@ def patch(self, request, *args, **kwargs):
]
DataSourceDepartmentUserRelation.objects.bulk_create(relations, ignore_conflicts=True)

# 【审计】记录变更后用户-部门映射
user_department_map = self.get_user_department_map(data_source_user_ids=data_source_user_ids)

# 【审计】创建用户-部门审计对象
audit_objects = [
AuditObject(
id=tenant_user.data_source_user_id,
name=tenant_user.data_source_user.username,
type=ObjectTypeEnum.DATA_SOURCE_USER,
operation=OperationEnum.MODIFY_USER_DEPARTMENT_RELATIONS,
data_before={"department_ids": user_department_map_before[tenant_user.data_source_user_id]},
data_after={"department_ids": user_department_map[tenant_user.data_source_user_id]},
extras={},
)
for tenant_user in TenantUser.objects.filter(
tenant_id=cur_tenant_id,
id__in=data["user_ids"],
).select_related("data_source_user")
]

# 【审计】批量添加审计记录
batch_add_audit_records(
operator=request.user.username,
tenant_id=cur_tenant_id,
objects=audit_objects,
)

return Response(status=status.HTTP_204_NO_CONTENT)


class TenantDeptUserRelationBatchDeleteApi(CurrentUserTenantDataSourceMixin, generics.DestroyAPIView):
class TenantDeptUserRelationBatchDeleteApi(
CurrentUserTenantDataSourceMixin, CurrentUserDepartmentRelationMixin, generics.DestroyAPIView
):
"""批量删除指定部门 & 用户的部门 - 用户关系"""

permission_classes = [IsAuthenticated, perm_class(PermAction.MANAGE_TENANT)]
Expand Down Expand Up @@ -191,8 +293,38 @@ def delete(self, request, *args, **kwargs):
id__in=data["user_ids"],
).values_list("data_source_user_id", flat=True)

# 【审计】记录变更前用户-部门映射
user_department_map_before = self.get_user_department_map(data_source_user_ids=data_source_user_ids)
Copy link
Collaborator

Choose a reason for hiding this comment

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

为审计预留的数据, 统一命名:

data_before_xxxxx
data_after_xxxx


DataSourceDepartmentUserRelation.objects.filter(
user_id__in=data_source_user_ids, department=source_data_source_dept
).delete()

# 【审计】记录变更后用户-部门映射
user_department_map = self.get_user_department_map(data_source_user_ids=data_source_user_ids)

# 【审计】创建用户-部门审计对象
audit_objects = [
AuditObject(
id=tenant_user.data_source_user_id,
name=tenant_user.data_source_user.username,
type=ObjectTypeEnum.DATA_SOURCE_USER,
operation=OperationEnum.MODIFY_USER_DEPARTMENT_RELATIONS,
data_before={"department_ids": user_department_map_before[tenant_user.data_source_user_id]},
data_after={"department_ids": user_department_map[tenant_user.data_source_user_id]},
extras={"department_id": source_data_source_dept.id},
)
for tenant_user in TenantUser.objects.filter(
tenant_id=cur_tenant_id,
id__in=data["user_ids"],
).select_related("data_source_user")
]

# 【审计】批量添加审计记录
batch_add_audit_records(
operator=request.user.username,
tenant_id=cur_tenant_id,
objects=audit_objects,
)

return Response(status=status.HTTP_204_NO_CONTENT)
Loading