From 7ba4ebffb15e664712a6887bf23c43b46b812fef Mon Sep 17 00:00:00 2001 From: rolinchen Date: Tue, 21 Jan 2025 17:46:25 +0800 Subject: [PATCH 1/4] feat: added batch query user sensitive informations --- .../bkuser/apis/open_v3/serializers/user.py | 17 +++++ src/bk-user/bkuser/apis/open_v3/urls.py | 5 ++ .../bkuser/apis/open_v3/views/__init__.py | 2 + src/bk-user/bkuser/apis/open_v3/views/user.py | 31 +++++++++ .../apidocs/en/list_user_sensitive_info.md | 68 +++++++++++++++++++ .../apidocs/zh/list_user_sensitive_info.md | 68 +++++++++++++++++++ src/bk-user/support-files/resources.yaml | 25 +++++++ src/bk-user/tests/apis/open_v3/test_user.py | 44 +++++++++++- 8 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md create mode 100644 src/bk-user/support-files/apidocs/zh/list_user_sensitive_info.md diff --git a/src/bk-user/bkuser/apis/open_v3/serializers/user.py b/src/bk-user/bkuser/apis/open_v3/serializers/user.py index 899369b66..c3c367609 100644 --- a/src/bk-user/bkuser/apis/open_v3/serializers/user.py +++ b/src/bk-user/bkuser/apis/open_v3/serializers/user.py @@ -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] diff --git a/src/bk-user/bkuser/apis/open_v3/urls.py b/src/bk-user/bkuser/apis/open_v3/urls.py index 4b6fcf4f3..c6f47dced 100644 --- a/src/bk-user/bkuser/apis/open_v3/urls.py +++ b/src/bk-user/bkuser/apis/open_v3/urls.py @@ -61,6 +61,11 @@ views.TenantDepartmentDescendantListApi.as_view(), name="open_v3.tenant_department.descendant.list", ), + path( + "users/-/sensitive-infos/", + views.TenantUserSensitiveInfoListApi.as_view(), + name="open_v3.tenant_user.sensitive_info.list", + ), ] ), ), diff --git a/src/bk-user/bkuser/apis/open_v3/views/__init__.py b/src/bk-user/bkuser/apis/open_v3/views/__init__.py index 8f2e69a91..9fc5e4a6f 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/__init__.py +++ b/src/bk-user/bkuser/apis/open_v3/views/__init__.py @@ -22,6 +22,7 @@ TenantUserLeaderListApi, TenantUserListApi, TenantUserRetrieveApi, + TenantUserSensitiveInfoListApi, ) __all__ = [ @@ -34,4 +35,5 @@ "TenantDepartmentRetrieveApi", "TenantDepartmentDescendantListApi", "TenantDepartmentListApi", + "TenantUserSensitiveInfoListApi", ] diff --git a/src/bk-user/bkuser/apis/open_v3/views/user.py b/src/bk-user/bkuser/apis/open_v3/views/user.py index 45a6af452..c5c7adf93 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/user.py +++ b/src/bk-user/bkuser/apis/open_v3/views/user.py @@ -32,6 +32,8 @@ TenantUserLeaderListOutputSLZ, TenantUserListOutputSLZ, TenantUserRetrieveOutputSLZ, + TenantUserSensitiveInfoListInputSLZ, + TenantUserSensitiveInfoListOutputSLZ, ) from bkuser.apps.data_source.models import ( DataSourceDepartment, @@ -238,3 +240,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).select_related( + "data_source_user" + ) + + @swagger_auto_schema( + tags=["open_v3.user"], + operation_id="list_user_sensitive_info.md", + 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) diff --git a/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md b/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md new file mode 100644 index 000000000..331b22eed --- /dev/null +++ b/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md @@ -0,0 +1,68 @@ +### 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 | + +# Response Example for Non-200 Status Code + +```json5 +// status_code = 400 +{ + "error": { + "code": "INVALID_ARGUMENT", + "message": "Arguments Validation Failed: bk_usernames: This field cannot be empty." + } +} +``` + +```json5 +// status_code = 400 +{ + "error": { + "code": "INVALID_ARGUMENT", + "message": "Arguments Validation Failed: bk_usernames: This field must contain at most 100 objects." + } +} +``` diff --git a/src/bk-user/support-files/apidocs/zh/list_user_sensitive_info.md b/src/bk-user/support-files/apidocs/zh/list_user_sensitive_info.md new file mode 100644 index 000000000..aa07402bf --- /dev/null +++ b/src/bk-user/support-files/apidocs/zh/list_user_sensitive_info.md @@ -0,0 +1,68 @@ +### 描述 + +批量查询用户敏感信息 + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|--------------|--------|----|----------------------------| +| 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 | 邮箱 | + +### 状态码非 200 的响应示例 + +```json5 +// status_code = 400 +{ + "error": { + "code": "INVALID_ARGUMENT", + "message": "参数校验不通过:bk_usernames: 该字段不能为空。" + } +} +``` + +```json5 +// status_code = 400 +{ + "error": { + "code": "INVALID_ARGUMENT", + "message": "参数校验不通过:bk_usernames: 至多包含 100 个对象。" + } +} +``` diff --git a/src/bk-user/support-files/resources.yaml b/src/bk-user/support-files/resources.yaml index 035ad3697..baf13bba3 100644 --- a/src/bk-user/support-files/resources.yaml +++ b/src/bk-user/support-files/resources.yaml @@ -231,3 +231,28 @@ paths: appVerifiedRequired: true resourcePermissionRequired: true descriptionEn: Query the list of sub-departments information based on 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 diff --git a/src/bk-user/tests/apis/open_v3/test_user.py b/src/bk-user/tests/apis/open_v3/test_user.py index 460b02898..5362689f8 100644 --- a/src/bk-user/tests/apis/open_v3/test_user.py +++ b/src/bk-user/tests/apis/open_v3/test_user.py @@ -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 @@ -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 From 2d8a5e503dfb2be29bd150f3b7643efb282c5e96 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Wed, 22 Jan 2025 17:11:10 +0800 Subject: [PATCH 2/4] feat: added batch query user sensitive informations --- src/bk-user/bkuser/apis/open_v3/views/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bk-user/bkuser/apis/open_v3/views/user.py b/src/bk-user/bkuser/apis/open_v3/views/user.py index c5c7adf93..d78ff80c1 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/user.py +++ b/src/bk-user/bkuser/apis/open_v3/views/user.py @@ -262,7 +262,7 @@ def get_queryset(self) -> QuerySet[TenantUser]: @swagger_auto_schema( tags=["open_v3.user"], - operation_id="list_user_sensitive_info.md", + operation_id="list_user_sensitive_info", operation_description="批量查询用户敏感信息", query_serializer=TenantUserSensitiveInfoListInputSLZ(), responses={status.HTTP_200_OK: TenantUserSensitiveInfoListOutputSLZ(many=True)}, From 76ea0c24ccda8f30caf5e1e6c0e7ccd53a332bb9 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Sat, 8 Feb 2025 17:49:12 +0800 Subject: [PATCH 3/4] resolve: solve some problems --- src/bk-user/bkuser/apis/open_v3/urls.py | 12 ++++++------ src/bk-user/bkuser/apis/open_v3/views/user.py | 6 +++--- .../apidocs/en/list_user_sensitive_info.md | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/bk-user/bkuser/apis/open_v3/urls.py b/src/bk-user/bkuser/apis/open_v3/urls.py index c6f47dced..483057295 100644 --- a/src/bk-user/bkuser/apis/open_v3/urls.py +++ b/src/bk-user/bkuser/apis/open_v3/urls.py @@ -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//", 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(), @@ -61,11 +66,6 @@ views.TenantDepartmentDescendantListApi.as_view(), name="open_v3.tenant_department.descendant.list", ), - path( - "users/-/sensitive-infos/", - views.TenantUserSensitiveInfoListApi.as_view(), - name="open_v3.tenant_user.sensitive_info.list", - ), ] ), ), diff --git a/src/bk-user/bkuser/apis/open_v3/views/user.py b/src/bk-user/bkuser/apis/open_v3/views/user.py index 1ac368cbb..ecd8b2c91 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/user.py +++ b/src/bk-user/bkuser/apis/open_v3/views/user.py @@ -264,9 +264,9 @@ def get_queryset(self) -> QuerySet[TenantUser]: slz.is_valid(raise_exception=True) data = slz.validated_data - return TenantUser.objects.filter(id__in=data["bk_usernames"], tenant_id=self.tenant_id).select_related( - "data_source_user" - ) + 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"], diff --git a/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md b/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md index 331b22eed..1c9181932 100644 --- a/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md +++ b/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md @@ -45,7 +45,7 @@ bk_usernames=7idwx3b7nzk6xigs,0wngfim3uzhadh1w | phone_country_code | string | Phone number area code | | email | string | Email address | -# Response Example for Non-200 Status Code +### Response Example for Non-200 Status Code ```json5 // status_code = 400 From 3a83aab1933b7ee6d08e9870d6bdd93b884f4d7f Mon Sep 17 00:00:00 2001 From: rolinchen Date: Sat, 8 Feb 2025 19:21:02 +0800 Subject: [PATCH 4/4] resolve: solve some problems --- .../apidocs/en/list_user_sensitive_info.md | 50 ++++++------------- .../apidocs/zh/list_user_sensitive_info.md | 50 ++++++------------- 2 files changed, 28 insertions(+), 72 deletions(-) diff --git a/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md b/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md index 1c9181932..441cba3d4 100644 --- a/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md +++ b/src/bk-user/support-files/apidocs/en/list_user_sensitive_info.md @@ -19,20 +19,20 @@ bk_usernames=7idwx3b7nzk6xigs,0wngfim3uzhadh1w ```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" - } - ] + "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" + } + ] } ``` @@ -44,25 +44,3 @@ bk_usernames=7idwx3b7nzk6xigs,0wngfim3uzhadh1w | phone | string | Phone number | | phone_country_code | string | Phone number area code | | email | string | Email address | - -### Response Example for Non-200 Status Code - -```json5 -// status_code = 400 -{ - "error": { - "code": "INVALID_ARGUMENT", - "message": "Arguments Validation Failed: bk_usernames: This field cannot be empty." - } -} -``` - -```json5 -// status_code = 400 -{ - "error": { - "code": "INVALID_ARGUMENT", - "message": "Arguments Validation Failed: bk_usernames: This field must contain at most 100 objects." - } -} -``` diff --git a/src/bk-user/support-files/apidocs/zh/list_user_sensitive_info.md b/src/bk-user/support-files/apidocs/zh/list_user_sensitive_info.md index aa07402bf..79363c8ce 100644 --- a/src/bk-user/support-files/apidocs/zh/list_user_sensitive_info.md +++ b/src/bk-user/support-files/apidocs/zh/list_user_sensitive_info.md @@ -19,20 +19,20 @@ bk_usernames=7idwx3b7nzk6xigs,0wngfim3uzhadh1w ```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" - } - ] + "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" + } + ] } ``` @@ -44,25 +44,3 @@ bk_usernames=7idwx3b7nzk6xigs,0wngfim3uzhadh1w | phone | string | 电话号码 | | phone_country_code | string | 电话号码区号 | | email | string | 邮箱 | - -### 状态码非 200 的响应示例 - -```json5 -// status_code = 400 -{ - "error": { - "code": "INVALID_ARGUMENT", - "message": "参数校验不通过:bk_usernames: 该字段不能为空。" - } -} -``` - -```json5 -// status_code = 400 -{ - "error": { - "code": "INVALID_ARGUMENT", - "message": "参数校验不通过:bk_usernames: 至多包含 100 个对象。" - } -} -```