Skip to content

Commit

Permalink
fix: 审计支持失败记录 TencentBlueKing#71
Browse files Browse the repository at this point in the history
  • Loading branch information
Canway-shiisa committed Oct 18, 2021
1 parent da57d13 commit bb6213e
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 226 deletions.
4 changes: 2 additions & 2 deletions src/api/bkuser_core/audit/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ class OperationEnum(AutoLowerEnum):


class OperationStatusEnum(AutoLowerEnum):
SUCCESS = auto()
SUCCEED = auto()
FAILED = auto()

_choices_labels = (
(SUCCESS, "成功"),
(SUCCEED, "成功"),
(FAILED, "失败"),
)
3 changes: 1 addition & 2 deletions src/api/bkuser_core/audit/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@

@receiver(post_profile_create)
@receiver(post_profile_update)
def create_audit_log(sender, profile, operator, operation_type, status, extra_values, **kwargs):
def create_audit_log(sender, profile, operator, operation_type, extra_values, **kwargs):
"""Create an audit log"""
request = extra_values["request"]
create_general_log(
operator=operator,
operate_type=operation_type,
operator_obj=profile,
request=request,
status=status,
)

# 当密码信息存在时,我们需要增加一条记录,
Expand Down
1 change: 1 addition & 0 deletions src/api/bkuser_core/audit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class Meta:

class GeneralLog(Log):
"""通用操作日志"""

status = models.CharField("状态", max_length=16, choices=OperationStatusEnum.get_choices())


Expand Down
31 changes: 26 additions & 5 deletions src/api/bkuser_core/audit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from django.conf import settings

from ..common.error_codes import CoreAPIError
from . import models
from .constants import OperationEnum, OperationStatusEnum
from .models import GeneralLog, ProfileRelatedLog
Expand Down Expand Up @@ -45,8 +46,8 @@ def get_client_ip(request: "Request") -> str:
def create_general_log(
operator: str,
operate_type: str,
status: str,
operator_obj: Any,
status: str = OperationStatusEnum.SUCCEED.value,
extra_info: Dict = None,
request=None,
) -> Optional[GeneralLog]:
Expand All @@ -61,10 +62,6 @@ def create_general_log(
logger.exception("operate type<%s> unknown", operate_type)
return None

if not OperationStatusEnum.has_value(status):
logger.exception("status <s%> unknown", status)
return None

extra_value = {
"operation": operate_type,
"obj_type": operator_obj.__class__.__name__,
Expand Down Expand Up @@ -98,3 +95,27 @@ def create_profile_log(
raise ValueError("unknown operation for profile log")
except Exception:
raise ValueError("operation is not a profile log type")


def audit_error_general_log(operate_type):
"""定义捕获异常的审计日志装饰器"""

def catch_exc(func):
def _catch_exc(self, request, *args, **kwargs):
try:
func(self, request, *args, **kwargs)

except CoreAPIError as e:
create_general_log(
operator=request.operator,
operate_type=operate_type,
operator_obj=self.get_object(),
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{e.message}"},
)
return

return _catch_exc

return catch_exc
109 changes: 9 additions & 100 deletions src/api/bkuser_core/categories/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import logging
from typing import List

from bkuser_core.audit.constants import OperationEnum, OperationStatusEnum
from bkuser_core.audit.utils import create_general_log
from bkuser_core.audit.constants import OperationEnum
from bkuser_core.audit.utils import audit_error_general_log, create_general_log
from bkuser_core.bkiam.permissions import IAMAction, IAMHelper, IAMPermissionExtraInfo, need_iam
from bkuser_core.categories.constants import CategoryType, SyncTaskType
from bkuser_core.categories.exceptions import ExistsSyncingTaskError, FetchDataFromRemoteFailed
Expand All @@ -39,7 +39,7 @@
from django.utils.decorators import method_decorator
from django.utils.module_loading import import_string
from drf_yasg.utils import swagger_auto_schema
from rest_framework import filters, status
from rest_framework import filters
from rest_framework.decorators import action
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.response import Response
Expand Down Expand Up @@ -112,20 +112,18 @@ def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = serializer.save()
headers = self.get_success_headers(serializer.data)
# headers = self.get_success_headers(serializer.data)

# 默认添加到最后 TODO: 需要一个更优雅的实现
max_order = ProfileCategory.objects.get_max_order()
instance.order = max_order + 1
instance.save(update_fields=["order"])

post_category_create.send(sender=self, category=instance, creator=request.operator)
create_general_log(
operator=request.operator,
operate_type=OperationEnum.CREATE.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.SUCCESS.value,
)

def get_serializer(self, *args, **kwargs):
Expand All @@ -134,6 +132,8 @@ def get_serializer(self, *args, **kwargs):
else:
return self.serializer_class(*args, **kwargs)

# @method_decorator(audit_error_general_log)
@audit_error_general_log(operate_type=OperationEnum.UPDATE.value)
@method_decorator(clear_cache_if_succeed)
def update(self, request, *args, **kwargs):
"""
Expand All @@ -148,25 +148,9 @@ def update(self, request, *args, **kwargs):

updating_domain = serializer.validated_data.get("domain")
if updating_domain and not updating_domain == instance.domain:
create_general_log(
operator=request.operator,
operate_type=OperationEnum.UPDATE.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{error_codes.CANNOT_UPDATE_DOMAIN.message}"},
)
raise error_codes.CANNOT_UPDATE_DOMAIN

if instance.default and any(serializer.validated_data.get(x) is False for x in ["enabled", "status"]):
create_general_log(
operator=request.operator,
operate_type=OperationEnum.UPDATE.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{error_codes.CANNOT_DISABLE_DOMAIN.message}"},
)
raise error_codes.CANNOT_DISABLE_DOMAIN

self.perform_update(serializer)
Expand All @@ -181,25 +165,17 @@ def update(self, request, *args, **kwargs):
operate_type=OperationEnum.UPDATE.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.SUCCESS.value,
)

return Response(serializer.data)

@audit_error_general_log(operate_type=OperationEnum.DELETE.value)
def destroy(self, request, *args, **kwargs):
"""
删除用户目录
"""
instance = self.get_object()
if instance.default:
create_general_log(
operator=request.operator,
operate_type=OperationEnum.DELETE.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{error_codes.CANNOT_DELETE_DEFAULT_CATEGORY.message}"},
)
raise error_codes.CANNOT_DELETE_DEFAULT_CATEGORY

post_category_delete.send(sender=self, category=instance, operator=request.operator)
Expand All @@ -208,7 +184,6 @@ def destroy(self, request, *args, **kwargs):
operate_type=OperationEnum.DELETE.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.SUCCESS.value,
)
return super().destroy(request, *args, **kwargs)

Expand Down Expand Up @@ -288,6 +263,7 @@ def test_fetch_data(self, request, lookup_value):

return Response()

@audit_error_general_log(operate_type=OperationEnum.SYNC.value)
@method_decorator(clear_cache_if_succeed)
@swagger_auto_schema(request_body=CategorySyncSerializer, responses={"200": CategorySyncResponseSLZ()})
def sync(self, request, lookup_value):
Expand All @@ -296,30 +272,13 @@ def sync(self, request, lookup_value):
self.check_object_permissions(request, instance)

if instance.type == CategoryType.LOCAL.value:
create_general_log(
operator=request.operator,
operate_type=OperationEnum.SYNC.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{error_codes.LOCAL_CATEGORY_CANNOT_SYNC.message}"}
)
raise error_codes.LOCAL_CATEGORY_CANNOT_SYNC

try:
task_id = SyncTask.objects.register_task(
category=instance, operator=request.operator, type_=SyncTaskType.MANUAL
).id
except ExistsSyncingTaskError as e:
create_general_log(
operator=request.operator,
operate_type=OperationEnum.SYNC.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{e}"}
)

raise error_codes.LOAD_DATA_FAILED.f(str(e))

try:
Expand All @@ -328,36 +287,18 @@ def sync(self, request, lookup_value):
)
except FetchDataFromRemoteFailed as e:
logger.exception("failed to sync data")
create_general_log(
operator=request.operator,
operate_type=OperationEnum.SYNC.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{e}"}
)

raise error_codes.SYNC_DATA_FAILED.f(f"{e}")
except CoreAPIError:
raise
except Exception:
logger.exception("failed to sync data")
create_general_log(
operator=request.operator,
operate_type=OperationEnum.SYNC.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{error_codes.SYNC_DATA_FAILED.message}"}
)
raise error_codes.SYNC_DATA_FAILED

create_general_log(
operator=request.operator,
operate_type=OperationEnum.SYNC.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.SUCCESS.value,
)
return Response({"task_id": task_id})

Expand All @@ -369,6 +310,7 @@ class CategoryFileViewSet(AdvancedModelViewSet, AdvancedListAPIView):
lookup_field = "id"
ordering = ["-create_time"]

@audit_error_general_log(operate_type=OperationEnum.IMPORT.value)
@method_decorator(clear_cache_if_succeed)
@swagger_auto_schema(request_body=CategorySyncSerializer, responses={"200": EmptySerializer()})
def import_data_file(self, request, lookup_value):
Expand All @@ -379,29 +321,13 @@ def import_data_file(self, request, lookup_value):
self.check_object_permissions(request, instance)

if instance.type != CategoryType.LOCAL.value:
create_general_log(
operator=request.operator,
operate_type=OperationEnum.IMPORT.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{error_codes.CATEGORY_CANNOT_IMPORT_BY_FILE.message}"}
)
raise error_codes.CATEGORY_CANNOT_IMPORT_BY_FILE

try:
task_id = SyncTask.objects.register_task(
category=instance, operator=request.operator, type_=SyncTaskType.MANUAL
).id
except ExistsSyncingTaskError as e:
create_general_log(
operator=request.operator,
operate_type=OperationEnum.IMPORT.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{e}"}
)
raise error_codes.LOAD_DATA_FAILED.f(str(e))

params = {"raw_data_file": serializer.validated_data["raw_data_file"]}
Expand All @@ -410,33 +336,16 @@ def import_data_file(self, request, lookup_value):
adapter_sync(lookup_value, operator=request.operator, task_id=task_id, **params)
except DataFormatError as e:
logger.exception("failed to sync data")
create_general_log(
operator=request.operator,
operate_type=OperationEnum.IMPORT.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{e}"}
)
raise error_codes.SYNC_DATA_FAILED.format(str(e), replace=True)
except Exception as e:
logger.exception("failed to sync data")
create_general_log(
operator=request.operator,
operate_type=OperationEnum.IMPORT.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.FAILED.value,
extra_info={"failed_info": f"{e}"}
)
raise error_codes.SYNC_DATA_FAILED.format(str(e), replace=True)

create_general_log(
operator=request.operator,
operate_type=OperationEnum.IMPORT.value,
operator_obj=instance,
request=request,
status=OperationStatusEnum.SUCCESS.value,
)
return Response()

Expand Down
Loading

0 comments on commit bb6213e

Please sign in to comment.