Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
27 changes: 25 additions & 2 deletions src/api/bkuser_core/api/login/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
ProfileSerializer,
)
from bkuser_core.audit.constants import LogInFailReason, OperationType
from bkuser_core.audit.utils import create_general_log, create_profile_log
from bkuser_core.audit.utils import create_general_log, create_profile_log, generate_profile_update_info
from bkuser_core.categories.constants import CategoryType
from bkuser_core.categories.loader import get_plugin_by_category
from bkuser_core.categories.models import ProfileCategory
Expand Down Expand Up @@ -104,6 +104,13 @@ def login(self, request):
config_loader = ConfigProvider(category_id=category.id)
# 由于安全检测等原因,取消原先对admin用户的检查豁免
if category.type in [CategoryType.LOCAL.value]:
# 判断账户解锁
auto_unlock_seconds = int(config_loader["auto_unlock_seconds"])
from_last_check_seconds = (time_aware_now - profile.latest_check_time).total_seconds()
retry_after_wait = int(auto_unlock_seconds - from_last_check_seconds)
if retry_after_wait <= 0 and profile.status == ProfileStatus.LOCKED.value:
profile.status = ProfileStatus.NORMAL.value
profile.save(update_fields=["status"])

# 判断账户状态
if profile.status in [
Expand Down Expand Up @@ -151,7 +158,6 @@ def login(self, request):
# raise error_codes.USER_IS_RESIGNED

# 获取密码配置
auto_unlock_seconds = int(config_loader["auto_unlock_seconds"])
max_trail_times = int(config_loader["max_trail_times"])

try:
Expand Down Expand Up @@ -195,6 +201,9 @@ def login(self, request):
profile.username,
message_detail,
)
# 将账户锁定
profile.status = ProfileStatus.LOCKED.value
profile.save(update_fields=["status"])
# NOTE: 安全原因, 不能返回账户状态
raise mask_login_error(error_codes.USER_LOCKED_TEMPORARILY.f(wait_seconds=retry_after_wait))
logger.exception("login check, check profile<%s> of %s failed", profile.username, message_detail)
Expand Down Expand Up @@ -310,6 +319,17 @@ def upsert(self, request):
else:
raise error_codes.DOMAIN_UNKNOWN

# 获取更新前的用户资料(用于变更日志)
try:
# 这里不需要预加载多对多字段, 因为这个接口无法修改相关信息
old_profile = Profile.objects.get(
username=username,
domain=category.domain,
category_id=category.id
)
except Profile.DoesNotExist:
old_profile = None

profile, created = Profile.objects.update_or_create(
username=username,
domain=category.domain,
Expand Down Expand Up @@ -342,6 +362,9 @@ def upsert(self, request):
operate_type=OperationType.CREATE.value if created else OperationType.UPDATE.value,
operator_obj=profile,
request=request,
extra_info={
"attributes": generate_profile_update_info(old_profile, profile)
},
)

return Response(data=ProfileSerializer(profile, context={"request": request}).data)
Expand Down
1 change: 1 addition & 0 deletions src/api/bkuser_core/api/web/audit/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def to_representation(self, obj):
"display_name": display_name,
"operation": instance["operation"],
"client_ip": extra_value.get("client_ip", PLACE_HOLDER),
"attributes": extra_value.get("attributes", []),
}


Expand Down
12 changes: 11 additions & 1 deletion src/api/bkuser_core/api/web/category/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"""
import logging
from typing import List
from functools import reduce
from operator import or_

from django.conf import settings
from django.db.models import Q
Expand Down Expand Up @@ -433,7 +435,7 @@ def get(self, request, *args, **kwargs):
mask_sensitive_data(item)


fields = DynamicFieldInfo.objects.filter(enabled=True).all()
fields = DynamicFieldInfo.objects.filter(enabled=True).exclude(name="operationtype").all()
fields_data = FieldOutputSLZ(fields, many=True).data
exporter = ProfileExcelExporter(
load_workbook(settings.EXPORT_ORG_TEMPLATE), settings.EXPORT_EXCEL_FILENAME + "_org", fields_data
Expand Down Expand Up @@ -590,6 +592,14 @@ def get_queryset(self):
request = self.request,
)

first_profile = queryset.first()
if first_profile:
create_general_log(
operator=self.request.operator,
operate_type=OperationType.VIEW.value,
operator_obj=first_profile,
request=self.request,
)
# do prefetch
queryset = queryset.prefetch_related("departments", "leader")
return queryset
Expand Down
38 changes: 31 additions & 7 deletions src/api/bkuser_core/api/web/department/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
DepartmentsWithChildrenAndAncestorsOutputSLZ,
)
from bkuser_core.api.web.utils import (
get_category,
get_default_category_id,
get_department,
get_category,
get_default_category_id,
get_department,
get_operator,
mask_sensitive_data
)
Expand Down Expand Up @@ -130,6 +130,29 @@ def delete(self, request, *args, **kwargs):
instance.delete()
return Response(status=status.HTTP_200_OK)

@audit_general_log(operate_type=OperationType.UPDATE.value)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data

# 名称不变的情况
if data["name"] != instance.name:
if Department.objects.filter(parent=instance.parent, name=data["name"]).exists():
raise error_codes.DEPARTMENT_NAME_CONFLICT

self.perform_update(serializer)

if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}

return Response(serializer.data)



class DepartmentSearchApi(generics.ListAPIView):
serializer_class = DepartmentSearchOutputSLZ
Expand Down Expand Up @@ -242,13 +265,14 @@ def list(self, request, *args, **kwargs):
if recursive:
queryset_recursive = queryset_recursive.filter(position=data["position"])
if data.get("leaders"):
if data["leaders"].isdigit():
queryset = queryset.filter(leader__id=data["leaders"])
leader_list = data.get("leaders").split(",")
if leader_list[0].isdigit():
queryset = queryset.filter(leader__id__in=leader_list)
else:
queryset = queryset.filter(leader__username__icontains=data["leaders"])
if recursive:
if data["leaders"].isdigit():
queryset_recursive = queryset_recursive.filter(leader__id=data["leaders"])
if leader_list[0].isdigit():
queryset_recursive = queryset_recursive.filter(leader__id__in=leader_list)
else:
queryset_recursive = queryset_recursive.filter(leader__username__icontains=data["leaders"])
if recursive:
Expand Down
2 changes: 2 additions & 0 deletions src/api/bkuser_core/api/web/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ def update_sheet_titles(self, exclude_keys: List[str] = []):
not_required_field_names = [
x["display_name"] for x in self.fields if not x["builtin"] and x["name"] not in exclude_keys
]
# 增加操作类型
not_required_field_names.append("操作类型")

field_col_map = {}

Expand Down
48 changes: 41 additions & 7 deletions src/api/bkuser_core/api/web/profile/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.core.exceptions import ObjectDoesNotExist
from django.forms.models import model_to_dict
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from rest_framework import generics, status
Expand All @@ -32,7 +33,7 @@
from bkuser_core.api.web.utils import get_category, get_operator, validate_password, mask_sensitive_data
from bkuser_core.api.web.viewset import CustomPagination
from bkuser_core.audit.constants import OperationType
from bkuser_core.audit.utils import audit_general_log, create_general_log
from bkuser_core.audit.utils import audit_general_log, create_general_log, generate_profile_update_info
from bkuser_core.bkiam.permissions import IAMAction, ManageDepartmentProfilePermission, Permission
from bkuser_core.categories.models import ProfileCategory
from bkuser_core.common.error_codes import error_codes
Expand Down Expand Up @@ -144,6 +145,13 @@ def _update(self, request, partial):
operate_type = OperationType.UPDATE.value
validated_data = slz.validated_data

# 记录旧的数据, 使用model_to_dict安全地进行转换
old_instance = Profile(**model_to_dict(instance, exclude=["leader", "departments"]))
old_instance.leader_list = instance.get_leader_list()
old_instance.departments_list = instance.get_departments_list()
# 复杂类型需要使用copy()
old_instance.extras = instance.extras.copy()

# 前端是把extras字段打平提交的
fields = DynamicFieldInfo.objects.filter(enabled=True).all()
extra_fields = {key: value for key, value in request.data.items() if key not in validated_data}
Expand Down Expand Up @@ -232,6 +240,10 @@ def should_skip_field(field_name):
logger.exception(f"failed to update profile<{username}>")
raise error_codes.SAVE_USER_INFO_FAILED.f(exception_message=e)

# 获取更新后的数据
instance.leader_list = instance.get_leader_list()
instance.departments_list = instance.get_departments_list()

post_profile_update.send(
sender=self,
instance=instance,
Expand All @@ -244,6 +256,9 @@ def should_skip_field(field_name):
operate_type=operate_type,
operator_obj=instance,
request=request,
extra_info={
"attributes": generate_profile_update_info(old_instance, instance)
}
)
return Response(self.serializer_class(instance).data)

Expand Down Expand Up @@ -474,18 +489,37 @@ def patch(self, request, *args, **kwargs):
category = ProfileCategory.objects.get(pk=instance.category_id)
Permission().allow_category_action(operator, IAMAction.MANAGE_CATEGORY, category)

create_general_log(
operator=operator,
operate_type=OperationType.UPDATE.value,
operator_obj=instance,
request=request,
)
# 拿到更新前的数据
old_instance = Profile(**model_to_dict(instance, exclude=["leader", "departments"]))
old_instance.leader_list = instance.get_leader_list()
old_instance.departments_list = instance.get_departments_list()
# 复杂类型需要使用copy()
old_instance.extras = instance.extras.copy()

# TODO: 限制非本地目录进行修改
single_serializer = ProfileBatchUpdateInputSLZ(instance=instance, data=obj)
single_serializer.is_valid(raise_exception=True)
single_serializer.save()
updating_instances.append(instance)

# 获取更新后的数据
instance.leader_list = instance.get_leader_list()
instance.departments_list = instance.get_departments_list()

# TODO: 限制非本地目录进行修改
single_serializer = ProfileBatchUpdateInputSLZ(instance=instance, data=obj)
single_serializer.is_valid(raise_exception=True)
single_serializer.save()
updating_instances.append(instance)

create_general_log(
operator=operator,
operate_type=OperationType.UPDATE.value,
operator_obj=instance,
request=request,
extra_info={
"attributes": generate_profile_update_info(old_instance, instance)
}
)

return Response(status=status.HTTP_200_OK)
2 changes: 1 addition & 1 deletion src/api/bkuser_core/api/web/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def mask_sensitive_data(data: dict) -> dict:

if data.get('telephone', ''):
data['telephone'] = mask_telephone(data['telephone'])

return data


Expand Down
Loading