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 operation audit for data source #1962

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
76 changes: 73 additions & 3 deletions src/bk-user/bkuser/apis/web/data_source/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
LocalDataSourceImportInputSLZ,
)
from bkuser.apis.web.mixins import CurrentUserTenantMixin
from bkuser.apps.audit.constants import OperationTarget, OperationType
from bkuser.apps.audit.service import add_operation_audit_record
from bkuser.apps.data_source.constants import DataSourceTypeEnum
from bkuser.apps.data_source.models import (
DataSource,
Expand Down Expand Up @@ -178,6 +180,15 @@ def post(self, request, *args, **kwargs):
updater=current_user,
)

add_operation_audit_record(
operator=current_user,
operation_target=OperationTarget.DATA_SOURCE,
operation_type=OperationType.CREATE_DATA_SOURCE,
tenant_id=current_tenant_id,
data_source_id=ds.id,
extras={"data_source_plugin_id": ds.plugin_id},
)

return Response(
DataSourceCreateOutputSLZ(instance={"id": ds.id}).data,
status=status.HTTP_201_CREATED,
Expand Down Expand Up @@ -238,6 +249,22 @@ def put(self, request, *args, **kwargs):
# 由于需要替换敏感信息,因此需要独立调用 set_plugin_cfg 方法
data_source.set_plugin_cfg(data["plugin_config"])

add_operation_audit_record(
operator=data_source.updater,
operation_target=OperationTarget.DATA_SOURCE,
operation_type=OperationType.MODIFY_DATA_SOURCE,
data_change={
"data_after": {
"plugin_config": data_source.plugin_config,
"field_mapping": data_source.field_mapping,
"sync_config": data_source.sync_config,
}
},
tenant_id=data_source.owner_tenant_id,
data_source_id=data_source.id,
extras={"data_source_plugin_id": data_source.plugin_id},
)

return Response(status=status.HTTP_204_NO_CONTENT)

@swagger_auto_schema(
Expand All @@ -255,9 +282,7 @@ def delete(self, request, *args, **kwargs):
slz = DataSourceDestroyInputSLZ(data=request.query_params)
slz.is_valid(raise_exception=True)
is_delete_idp = slz.validated_data["is_delete_idp"]

# TODO (su) 支持操作审计后可删除该日志
logger.warning("user %s delete data source %s", request.user.username, data_source.id)
data_source_id = data_source.id

with transaction.atomic():
if is_delete_idp:
Expand Down Expand Up @@ -285,6 +310,15 @@ def delete(self, request, *args, **kwargs):
# 删除数据源 & 关联资源数据
DataSourceHandler.delete_data_source_and_related_resources(data_source)

add_operation_audit_record(
operator=request.user.username,
operation_target=OperationTarget.DATA_SOURCE,
operation_type=OperationType.DELETE_DATA_SOURCE,
tenant_id=self.get_current_tenant_id(),
data_source_id=data_source_id,
extras={"data_source_plugin_id": data_source.plugin_id, "is_delete_idp": is_delete_idp},
)

return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down Expand Up @@ -478,6 +512,28 @@ def post(self, request, *args, **kwargs):
logger.exception("本地数据源 %s 导入失败", data_source.id)
raise error_codes.DATA_SOURCE_IMPORT_FAILED.f(str(e))

add_operation_audit_record(
operator=request.user.username,
operation_target=OperationTarget.DATA_SOURCE,
operation_type=OperationType.IMPORT_DATA_SOURCE,
tenant_id=data_source.owner_tenant_id,
data_source_id=data_source.id,
)

add_operation_audit_record(
operator=task.operator,
operation_target=OperationTarget.DATA_SOURCE,
operation_type=OperationType.SYNC_DATA_SOURCE,
tenant_id=data_source.owner_tenant_id,
data_source_id=data_source.id,
extras={
"task_id": task.id,
"overwrite": data["overwrite"],
"incremental": data["incremental"],
"data_source_plugin_id": data_source.plugin_id,
},
)

return Response(
DataSourceImportOrSyncOutputSLZ(
instance={"task_id": task.id, "status": task.status, "summary": task.summary}
Expand Down Expand Up @@ -521,6 +577,20 @@ def post(self, request, *args, **kwargs):
logger.exception("创建下发数据源 %s 同步任务失败", data_source.id)
raise error_codes.DATA_SOURCE_SYNC_TASK_CREATE_FAILED.f(str(e))

add_operation_audit_record(
operator=task.operator,
operation_target=OperationTarget.DATA_SOURCE,
operation_type=OperationType.SYNC_DATA_SOURCE,
tenant_id=data_source.owner_tenant_id,
data_source_id=data_source.id,
extras={
"task_id": task.id,
"overwrite": True,
"incremental": False,
"data_source_plugin_id": data_source.plugin_id,
},
)

return Response(
DataSourceImportOrSyncOutputSLZ(
instance={"task_id": task.id, "status": task.status, "summary": task.summary}
Expand Down
10 changes: 10 additions & 0 deletions src/bk-user/bkuser/apps/audit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
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.
"""
17 changes: 17 additions & 0 deletions src/bk-user/bkuser/apps/audit/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
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 django.apps import AppConfig


class AuditConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "bkuser.apps.audit"
71 changes: 71 additions & 0 deletions src/bk-user/bkuser/apps/audit/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
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 blue_krill.data_types.enum import EnumField, StructuredEnum
from django.utils.translation import gettext_lazy as _


class OperationTarget(str, StructuredEnum):
"""操作对象"""

DATA_SOURCE = EnumField("data_source", label=_("数据源"))
IDP = EnumField("idp", label=_("认证源"))
USER = EnumField("user", label=_("用户"))
ORGANIZATION = EnumField("organization", label=_("组织"))
MANAGEMENT_PLATFORM = EnumField("management_platform", label=_("管理平台"))
TENANT = EnumField("tenant", label=_("租户"))
VIRTUAL_USER = EnumField("virtual_user", label=_("虚拟用户"))


class OperationType(str, StructuredEnum):
"""操作类型"""

# 数据源
CREATE_DATA_SOURCE = EnumField("create_data_source", label=_("创建数据源"))
MODIFY_DATA_SOURCE = EnumField("modify_data_source", label=_("修改数据源"))
DELETE_DATA_SOURCE = EnumField("delete_data_source", label=_("删除数据源"))
SYNC_DATA_SOURCE = EnumField("sync_data_source", label=_("同步数据源"))
IMPORT_DATA_SOURCE = EnumField("import_data_source", label=_("导入数据源"))
# 认证源
CREATE_IDP = EnumField("create_idp", label=_("创建认证源"))
MODIFY_IDP = EnumField("modify_idp", label=_("修改认证源"))
MODIFY_IDP_STATUS = EnumField("modify_idp_status", label=_("修改认证源状态"))
# 用户
CREATE_USER = EnumField("create_user", label=_("创建用户"))
MODIFY_USER = EnumField("modify_user", label=_("修改用户信息"))
DELETE_USER = EnumField("delete_user", label=_("删除用户"))
MODIFY_USER_ORGANIZATION_RELATIONS = EnumField("modify_user_organization_relations", label=_("修改用户所属组织"))
MODIFY_USER_STATUS = EnumField("modify_user_status", label=_("修改用户状态"))
MODIFY_ACCOUNT_EXPIRED_AT = EnumField("modify_account_expired_at", label=_("修改账户过期时间"))
MODIFY_USER_LEADERS = EnumField("modify_user_leaders", label=_("修改用户上级"))
MODIFY_USER_PASSWORD = EnumField("modify_user_password", label=_("重置用户密码"))
MODIFY_USER_EMAIL = EnumField("modify_user_email", label=_("修改用户邮箱"))
MODIFY_USER_PHONE = EnumField("modify_user_phone", label=_("修改用户电话号码"))
SEND_EMAIL_VERIFICATION_CODE = EnumField("send_email_verification_code", label=_("发送邮箱验证码"))
SEND_PHONE_VERIFICATION_CODE = EnumField("send_phone_verification_code", label=_("发送手机验证码"))
# 组织
CREATE_ORGANIZATION = EnumField("create_organization", label=_("创建组织"))
rolin999 marked this conversation as resolved.
Show resolved Hide resolved
MODIFY_ORGANIZATION = EnumField("modify_organization", label=_("修改组织名称"))
DELETE_ORGANIZATION = EnumField("delete_organization", label=_("删除组织"))
MODIFY_PARENT_ORGANIZATION = EnumField("modify_parent_organization", label=_("修改上级组织"))
# 管理平台
CREATE_TENANT = EnumField("create_tenant", label=_("创建租户"))
MODIFY_TENANT = EnumField("modify_tenant", label=_("修改租户信息"))
DELETE_TENANT = EnumField("delete_tenant", label=_("删除租户"))
MODIFY_TENANT_STATUS = EnumField("modify_tenant_status", label=_("修改租户状态"))
# 租户
CREATE_REAL_MANAGER = EnumField("create_real_manager", label=_("创建实名管理员"))
DELETE_REAL_MANAGER = EnumField("delete_real_manager", label=_("删除实名管理员"))
MODIFY_VALIDITY_PERIOD_CONFIG = EnumField("modify_validity_period_config", label=_("修改租户账户有效期配置"))
# 虚拟用户
CREATE_VIRTUAL_USER = EnumField("create_virtual_user", label=_("创建虚拟用户"))
MODIFY_VIRTUAL_USER = EnumField("modify_virtual_user", label=_("修改虚拟用户信息"))
DELETE_VIRTUAL_USER = EnumField("delete_virtual_user", label=_("删除虚拟用户"))
34 changes: 34 additions & 0 deletions src/bk-user/bkuser/apps/audit/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 3.2.25 on 2024-10-17 09:04

from django.db import migrations, models
import uuid


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='OperationAuditRecord',
fields=[
('id', models.UUIDField(auto_created=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='事件 id')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('creator', models.CharField(blank=True, max_length=128, null=True)),
('updater', models.CharField(blank=True, max_length=128, null=True)),
('operation_target', models.CharField(max_length=32, verbose_name='操作对象')),
('operation_type', models.CharField(max_length=32, verbose_name='操作类型')),
('tenant_id', models.CharField(max_length=32, verbose_name='操作对象所属的租户 id')),
('data_change', models.JSONField(blank=True, max_length=32, null=True, verbose_name='操作数据变更')),
('data_source_id', models.IntegerField(blank=True, max_length=32, null=True, verbose_name='操作对象所属的数据源 id')),
('extras', models.JSONField(blank=True, null=True, verbose_name='操作额外信息')),
],
options={
'ordering': ['-created_at'],
},
),
]
Empty file.
31 changes: 31 additions & 0 deletions src/bk-user/bkuser/apps/audit/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
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.
"""

import uuid

from django.db import models

from bkuser.common.models import AuditedModel


class OperationAuditRecord(AuditedModel):
id = models.UUIDField(
"事件 id", default=uuid.uuid4, primary_key=True, editable=False, auto_created=True, unique=True
)
operation_target = models.CharField(max_length=32, verbose_name="操作对象")
operation_type = models.CharField(max_length=32, verbose_name="操作类型")
tenant_id = models.CharField(max_length=32, verbose_name="操作对象所属的租户 id")
data_change = models.JSONField(max_length=32, verbose_name="操作数据变更", null=True, blank=True)
data_source_id = models.IntegerField(max_length=32, verbose_name="操作对象所属的数据源 id", null=True, blank=True)
extras = models.JSONField(verbose_name="操作额外信息", null=True, blank=True)

class Meta:
ordering = ["-created_at"] # 按照 created_at 字段降序排列
35 changes: 35 additions & 0 deletions src/bk-user/bkuser/apps/audit/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
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 typing import Dict

from .constants import OperationTarget, OperationType
from .models import OperationAuditRecord


def add_operation_audit_record(
operator: str,
operation_target: OperationTarget,
operation_type: OperationType,
tenant_id: str,
data_change: Dict | None = None,
data_source_id: int | None = None,
extras: Dict | None = None,
) -> OperationAuditRecord:
return OperationAuditRecord.objects.create(
creator=operator,
operation_target=operation_target,
operation_type=operation_type,
tenant_id=tenant_id,
data_change=data_change,
data_source_id=data_source_id,
extras=extras,
)
1 change: 1 addition & 0 deletions src/bk-user/bkuser/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"django_prometheus",
"drf_yasg",
"bkuser.auth",
"bkuser.apps.audit",
"bkuser.apps.data_source",
"bkuser.apps.tenant",
"bkuser.apps.sync",
Expand Down