Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: added batch query user sensitive informations #2041

Merged
merged 6 commits into from
Feb 10, 2025
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
17 changes: 17 additions & 0 deletions src/bk-user/bkuser/apis/open_v3/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,20 @@ class TenantUserListOutputSLZ(serializers.Serializer):

def get_display_name(self, obj: TenantUser) -> str:
return TenantUserHandler.generate_tenant_user_display_name(obj)


class TenantUserSensitiveInfoListInputSLZ(serializers.Serializer):
bk_usernames = StringArrayField(help_text="蓝鲸用户唯一标识,多个使用逗号分隔", max_items=100)


class TenantUserSensitiveInfoListOutputSLZ(serializers.Serializer):
bk_username = serializers.CharField(help_text="蓝鲸用户唯一标识", source="id")
phone = serializers.SerializerMethodField(help_text="手机号")
phone_country_code = serializers.SerializerMethodField(help_text="手机国际区号")
email = serializers.CharField(help_text="邮箱")

def get_phone(self, obj: TenantUser) -> str:
return obj.phone_info[0]

def get_phone_country_code(self, obj: TenantUser) -> str:
return obj.phone_info[1]
7 changes: 6 additions & 1 deletion src/bk-user/bkuser/apis/open_v3/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,17 @@
views.TenantUserLeaderListApi.as_view(),
name="open_v3.tenant_user.leader.list",
),
path(
"users/-/sensitive-infos/",
views.TenantUserSensitiveInfoListApi.as_view(),
name="open_v3.tenant_user.sensitive_info.list",
),
path("users/", views.TenantUserListApi.as_view(), name="open_v3.tenant_user.list"),
path(
"departments/<int:id>/",
views.TenantDepartmentRetrieveApi.as_view(),
name="open_v3.tenant_department.retrieve",
),
path("users/", views.TenantUserListApi.as_view(), name="open_v3.tenant_user.list"),
path(
"departments/",
views.TenantDepartmentListApi.as_view(),
Expand Down
2 changes: 2 additions & 0 deletions src/bk-user/bkuser/apis/open_v3/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
TenantUserLeaderListApi,
TenantUserListApi,
TenantUserRetrieveApi,
TenantUserSensitiveInfoListApi,
)

__all__ = [
Expand All @@ -40,4 +41,5 @@
"TenantDepartmentDescendantListApi",
"TenantDepartmentListApi",
"TenantDepartmentUserListApi",
"TenantUserSensitiveInfoListApi",
]
31 changes: 31 additions & 0 deletions src/bk-user/bkuser/apis/open_v3/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
TenantUserLeaderListOutputSLZ,
TenantUserListOutputSLZ,
TenantUserRetrieveOutputSLZ,
TenantUserSensitiveInfoListInputSLZ,
TenantUserSensitiveInfoListOutputSLZ,
)
from bkuser.apps.data_source.models import (
DataSourceDepartment,
Expand Down Expand Up @@ -246,3 +248,32 @@ def get_queryset(self) -> QuerySet[TenantUser]:
)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)


class TenantUserSensitiveInfoListApi(OpenApiCommonMixin, generics.ListAPIView):
"""
根据 bk_username 批量查询用户敏感信息
"""

pagination_class = None

serializer_class = TenantUserSensitiveInfoListOutputSLZ

def get_queryset(self) -> QuerySet[TenantUser]:
slz = TenantUserSensitiveInfoListInputSLZ(data=self.request.query_params)
slz.is_valid(raise_exception=True)
data = slz.validated_data

return TenantUser.objects.filter(
id__in=data["bk_usernames"], tenant_id=self.tenant_id, data_source_id=self.real_data_source_id
).select_related("data_source_user")

@swagger_auto_schema(
tags=["open_v3.user"],
operation_id="list_user_sensitive_info",
operation_description="批量查询用户敏感信息",
query_serializer=TenantUserSensitiveInfoListInputSLZ(),
responses={status.HTTP_200_OK: TenantUserSensitiveInfoListOutputSLZ(many=True)},
)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
46 changes: 46 additions & 0 deletions src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
### Description

Batch query user's sensitive information

### Parameters

| Name | Type | Required | Description |
|--------------|--------|----------|-------------------------------------------------------------------------------------------------------|
| bk_usernames | string | Yes | Blueking user's unique identifier, multiple identifiers are separated by commas, and the limit is 100 |

### Request Example

```
// URL Query Parameters
bk_usernames=7idwx3b7nzk6xigs,0wngfim3uzhadh1w
```

### Response Example for Status Code 200

```json5
{
"data": [
{
"bk_username": "7idwx3b7nzk6xigs",
"phone": "17712341234",
"phone_country_code": "86",
"email": "zhangsan@qq.com"
},
{
"bk_username": "0wngfim3uzhadh1w",
"phone": "18712341234",
"phone_country_code": "86",
"email": "lisi@qq.com"
}
]
}
```

### Response Parameters Description

| Name | Type | Description |
|--------------------|--------|-----------------------------------|
| bk_username | string | Blueking user's unique identifier |
| phone | string | Phone number |
| phone_country_code | string | Phone number area code |
| email | string | Email address |
46 changes: 46 additions & 0 deletions src/bk-user/support-files/apidocs/zh/list_user_sensitive_info.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
### 描述

批量查询用户敏感信息

### 输入参数

| 参数名称 | 参数类型 | 必选 | 描述 |
|--------------|--------|----|----------------------------|
| bk_usernames | string | 是 | 蓝鲸用户唯一标识,多个以逗号分隔,限制数量为 100 |

### 请求示例

```
// URL Query 参数
bk_usernames=7idwx3b7nzk6xigs,0wngfim3uzhadh1w
```

### 状态码 200 的响应示例

```json5
{
"data": [
{
"bk_username": "7idwx3b7nzk6xigs",
"phone": "17712341234",
"phone_country_code": "86",
"email": "zhangsan@qq.com"
},
{
"bk_username": "0wngfim3uzhadh1w",
"phone": "18712341234",
"phone_country_code": "86",
"email": "lisi@qq.com"
}
]
}
```

### 响应参数说明

| 参数名称 | 参数类型 | 描述 |
|--------------------|--------|----------|
| bk_username | string | 蓝鲸用户唯一标识 |
| phone | string | 电话号码 |
| phone_country_code | string | 电话号码区号 |
| email | string | 邮箱 |
25 changes: 25 additions & 0 deletions src/bk-user/support-files/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,28 @@ paths:
appVerifiedRequired: true
resourcePermissionRequired: true
descriptionEn: Query the user list under the department according to the department ID

/api/v3/open/tenant/users/-/sensitive-infos/:
get:
operationId: list_user_sensitive_info
description: 批量查询用户敏感信息
tags: []
responses:
default:
description: ''
x-bk-apigateway-resource:
isPublic: true
allowApplyPermission: false
matchSubpath: false
backend:
name: default
method: get
path: /api/v3/open/tenant/users/-/sensitive-infos/
matchSubpath: false
timeout: 0
pluginConfigs: []
authConfig:
userVerifiedRequired: false
appVerifiedRequired: true
resourcePermissionRequired: true
descriptionEn: Batch query user's sensitive information
44 changes: 43 additions & 1 deletion src/bk-user/tests/apis/open_v3/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def test_with_invalid_user(self, api_client):

@pytest.mark.usefixtures("_init_tenant_users_depts")
class TestTenantUserListApi:
def test_standard(self, api_client, random_tenant):
def test_standard(self, api_client):
resp = api_client.get(reverse("open_v3.tenant_user.list"), data={"page": 1, "page_size": 11})
assert resp.status_code == status.HTTP_200_OK
assert resp.data["count"] == 11
Expand Down Expand Up @@ -195,3 +195,45 @@ def test_standard(self, api_client, random_tenant):
"白十二",
"自由人",
}


@pytest.mark.usefixtures("_init_tenant_users_depts")
class TestTenantUserSensitiveInfoListApi:
def test_list_tenant_user(self, api_client):
zhangsan = TenantUser.objects.get(data_source_user__username="zhangsan")
lisi = TenantUser.objects.get(data_source_user__username="lisi")
resp = api_client.get(
reverse("open_v3.tenant_user.sensitive_info.list"), data={"bk_usernames": ",".join([zhangsan.id, lisi.id])}
)

assert resp.status_code == status.HTTP_200_OK
assert len(resp.data) == 2
assert {t["bk_username"] for t in resp.data} == {zhangsan.id, lisi.id}
assert {t["phone"] for t in resp.data} == {"13512345671", "13512345672"}
assert {t["email"] for t in resp.data} == {"zhangsan@m.com", "lisi@m.com"}
assert {t["phone_country_code"] for t in resp.data} == {"86"}

def test_with_invalid_bk_usernames(self, api_client):
zhangsan = TenantUser.objects.get(data_source_user__username="zhangsan")
resp = api_client.get(
reverse("open_v3.tenant_user.sensitive_info.list"),
data={"bk_usernames": ",".join([zhangsan.id, "invalid"])},
)

assert resp.status_code == status.HTTP_200_OK
assert len(resp.data) == 1
assert resp.data[0]["bk_username"] == zhangsan.id
assert resp.data[0]["phone"] == "13512345671"
assert resp.data[0]["email"] == "zhangsan@m.com"
assert resp.data[0]["phone_country_code"] == "86"

def test_with_no_bk_usernames(self, api_client):
resp = api_client.get(reverse("open_v3.tenant_user.sensitive_info.list"), data={"bk_usernames": ""})
assert resp.status_code == status.HTTP_400_BAD_REQUEST

def test_with_invalid_length(self, api_client):
resp = api_client.get(
reverse("open_v3.tenant_user.sensitive_info.list"),
data={"bk_usernames": ",".join(map(str, range(1, 102)))},
)
assert resp.status_code == status.HTTP_400_BAD_REQUEST