Skip to content

Commit

Permalink
merge: merged ft_tenant
Browse files Browse the repository at this point in the history
  • Loading branch information
rolin999 committed Jan 17, 2025
2 parents 08a9147 + 14d7289 commit ab5805f
Show file tree
Hide file tree
Showing 15 changed files with 359 additions and 12 deletions.
41 changes: 41 additions & 0 deletions src/bk-user/bkuser/apis/apigw/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# -*- 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.
#
# We undertake not to change the open source license (MIT license) applicable
# to the current version of the project delivered to anyone in the future.
from functools import cached_property

from rest_framework.exceptions import ValidationError
from rest_framework.request import Request

from bkuser.apis.apigw.authentications import InnerBearerTokenAuthentication
from bkuser.apis.apigw.permissions import IsInnerBearerTokenAuthenticated


class InnerApiCommonMixin:
authentication_classes = [InnerBearerTokenAuthentication]
permission_classes = [IsInnerBearerTokenAuthenticated]

request: Request

TenantHeaderKey = "HTTP_X_BK_TENANT_ID"

@cached_property
def tenant_id(self) -> str:
tenant_id = self.request.META.get(self.TenantHeaderKey)

if not tenant_id:
raise ValidationError("X-Bk-Tenant-Id header is required")

return tenant_id
43 changes: 43 additions & 0 deletions src/bk-user/bkuser/apis/apigw/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# -*- 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.
#
# We undertake not to change the open source license (MIT license) applicable
# to the current version of the project delivered to anyone in the future.
from rest_framework import serializers

from bkuser.apps.tenant.models import TenantUser
from bkuser.biz.tenant import TenantUserHandler
from bkuser.common.serializers import StringArrayField


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


class TenantUserContactInfoListOutputSLZ(serializers.Serializer):
bk_username = serializers.CharField(help_text="蓝鲸用户唯一标识", source="id")
tenant_id = serializers.CharField(help_text="租户 ID")
display_name = serializers.SerializerMethodField(help_text="用户展示名称")
phone = serializers.SerializerMethodField(help_text="手机号")
phone_country_code = serializers.SerializerMethodField(help_text="手机国际区号")
email = serializers.CharField(help_text="邮箱")

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

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]
5 changes: 5 additions & 0 deletions src/bk-user/bkuser/apis/apigw/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@
path(
"tenant-users/<str:tenant_user_id>/", views.TenantUserRetrieveApi.as_view(), name="apigw.tenant_user.retrieve"
),
path(
"tenant-users/-/contact-infos/",
views.TenantUserContactInfoListApi.as_view(),
name="apigw.tenant_user.contact_info.list",
),
]
32 changes: 26 additions & 6 deletions src/bk-user/bkuser/apis/apigw/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,24 @@
#
# We undertake not to change the open source license (MIT license) applicable
# to the current version of the project delivered to anyone in the future.
from django.db.models import QuerySet
from rest_framework import generics
from rest_framework.response import Response

from bkuser.apps.tenant.models import TenantUser
from bkuser.common.error_codes import error_codes

from .authentications import InnerBearerTokenAuthentication
from .permissions import IsInnerBearerTokenAuthenticated
from .mixins import InnerApiCommonMixin
from .serializers import TenantUserContactInfoListInputSLZ, TenantUserContactInfoListOutputSLZ


class TenantUserRetrieveApi(generics.RetrieveAPIView):
class TenantUserRetrieveApi(InnerApiCommonMixin, generics.RetrieveAPIView):
"""
查询用户信息
Note: 网关内部接口对性能要求较高,所以不进行序列化,且查询必须按字段
TODO:后续根据耗时统计进行 Cache 优化
"""

authentication_classes = [InnerBearerTokenAuthentication]
permission_classes = [IsInnerBearerTokenAuthenticated]

def get(self, request, *args, **kwargs):
tenant_user_id = kwargs["tenant_user_id"]

Expand All @@ -43,3 +41,25 @@ def get(self, request, *args, **kwargs):
raise error_codes.OBJECT_NOT_FOUND.f(f"user({tenant_user_id}) not found", replace=True)

return Response({"tenant_id": tenant_user.tenant_id})


class TenantUserContactInfoListApi(InnerApiCommonMixin, generics.ListAPIView):
"""
根据 bk_username 批量查询用户联系方式
"""

pagination_class = None

serializer_class = TenantUserContactInfoListOutputSLZ

def get_queryset(self) -> QuerySet[TenantUser]:
slz = TenantUserContactInfoListInputSLZ(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"
)

def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
9 changes: 9 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 @@ -73,3 +73,12 @@ class TenantUserLeaderListOutputSLZ(serializers.Serializer):

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


class TenantUserListOutputSLZ(serializers.Serializer):
bk_username = serializers.CharField(help_text="蓝鲸用户唯一标识", source="id")
full_name = serializers.CharField(help_text="姓名", source="data_source_user.full_name")
display_name = serializers.SerializerMethodField(help_text="用户展示名称")

def get_display_name(self, obj: TenantUser) -> str:
return TenantUserHandler.generate_tenant_user_display_name(obj)
1 change: 1 addition & 0 deletions src/bk-user/bkuser/apis/open_v3/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
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 @@ -20,6 +20,7 @@
TenantUserDepartmentListApi,
TenantUserDisplayNameListApi,
TenantUserLeaderListApi,
TenantUserListApi,
TenantUserRetrieveApi,
)

Expand All @@ -29,6 +30,7 @@
"TenantUserRetrieveApi",
"TenantUserDepartmentListApi",
"TenantUserLeaderListApi",
"TenantUserListApi",
"TenantDepartmentRetrieveApi",
"TenantDepartmentListApi",
]
29 changes: 29 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 @@ -30,6 +30,7 @@
TenantUserDisplayNameListInputSLZ,
TenantUserDisplayNameListOutputSLZ,
TenantUserLeaderListOutputSLZ,
TenantUserListOutputSLZ,
TenantUserRetrieveOutputSLZ,
)
from bkuser.apps.data_source.models import (
Expand All @@ -39,6 +40,7 @@
)
from bkuser.apps.tenant.models import TenantDepartment, TenantUser
from bkuser.biz.organization import DataSourceDepartmentHandler
from bkuser.common.pagination import CustomPageNumberPagination

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -209,3 +211,30 @@ def get_queryset(self) -> QuerySet[TenantUser]:
)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)


class TenantUserListApi(OpenApiCommonMixin, generics.ListAPIView):
"""
查询用户列表
"""

pagination_class = CustomPageNumberPagination
pagination_class.max_page_size = 1000

serializer_class = TenantUserListOutputSLZ

def get_queryset(self) -> QuerySet[TenantUser]:
return (
TenantUser.objects.select_related("data_source_user")
.filter(tenant_id=self.tenant_id)
.only("id", "data_source_user__full_name")
)

@swagger_auto_schema(
tags=["open_v3.user"],
operation_id="list_user",
operation_description="查询用户列表",
responses={status.HTTP_200_OK: TenantUserListOutputSLZ(many=True)},
)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
47 changes: 47 additions & 0 deletions src/bk-user/support-files/apidocs/en/list_user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
### Description

(Pagination) Query user's list

### Parameters

| Name | Type | Required | Description |
|-----------|------|----------|--------------------------------------------------------------|
| page | int | No | Page number, default is 1 |
| page_size | int | No | The number of pages per page, default is 10, maximum is 1000 |

### Request Example

```
// URL Query Parameters
page=1&page_size=5
```

### Response Example for Status Code 200

```json5
{
"data": {
"count": 2,
"results": [
{
"bk_username": "q9k6bhqks0ckl5ew",
"full_name": "张三",
"display_name": "张三",
},
{
"bk_username": "er0ugcammqwf1q5w",
"full_name": "李四",
"display_name": "李四",
}
],
}
}
```

### Response Parameters Description

| Name | Type | Description |
|--------------|--------|-----------------------------------|
| bk_username | string | Blueking user's unique identifier |
| full_name | string | User's name |
| display_name | string | User's display name |
47 changes: 47 additions & 0 deletions src/bk-user/support-files/apidocs/zh/list_user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
### 描述

(分页)查询用户列表

### 输入参数

| 参数名称 | 参数类型 | 必选 | 描述 |
|-----------|------|----|--------------------|
| page | int || 页码,从 1 开始 |
| page_size | int || 每页数量,默认 10,最大 1000 |

### 请求示例

```
// URL Query 参数
page=1&page_size=5
```

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

```json5
{
"data": {
"count": 2,
"results": [
{
"bk_username": "q9k6bhqks0ckl5ew",
"full_name": "张三",
"display_name": "张三",
},
{
"bk_username": "er0ugcammqwf1q5w",
"full_name": "李四",
"display_name": "李四",
}
],
}
}
```

### 响应参数说明

| 参数名称 | 参数类型 | 描述 |
|--------------|--------|----------|
| bk_username | string | 蓝鲸用户唯一标识 |
| full_name | string | 用户姓名 |
| display_name | string | 用户展示名 |
8 changes: 4 additions & 4 deletions src/bk-user/support-files/apidocs/zh/retrieve_department.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

### 输入参数

| 参数名称 | 参数类型 | 必选 | 参数位置|描述 |
|----------------|---------|----|----------------------------|
| department_id | int || path |部门唯一标识 |
| with_ancestors | boolean || query param | 是否包括祖先部门,默认为 false |
| 参数名称 | 参数类型 | 必选 | 参数位置 | 描述 |
|----------------|---------|----|-------------|--------------------|
| department_id | int | | path | 部门唯一标识 |
| with_ancestors | boolean | | query param | 是否包括祖先部门,默认为 false |

### 请求示例

Expand Down
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 @@ -157,6 +157,31 @@ paths:
resourcePermissionRequired: true
descriptionEn: Query information of the department

/api/v3/open/tenant/users/:
get:
operationId: list_user
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/
matchSubpath: false
timeout: 0
pluginConfigs: []
authConfig:
userVerifiedRequired: false
appVerifiedRequired: true
resourcePermissionRequired: true
descriptionEn: (Pagination) Query user's list

/api/v3/open/tenant/departments/:
get:
operationId: list_department
Expand Down
3 changes: 2 additions & 1 deletion src/bk-user/tests/apis/apigw/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ def mock_token_authenticate(self, request):


@pytest.fixture
def apigw_api_client() -> APIClient:
def apigw_api_client(default_tenant) -> APIClient:
client = APIClient()
client.defaults["HTTP_X_BK_TENANT_ID"] = default_tenant.id

with mock.patch(
"bkuser.apis.apigw.authentications.InnerBearerTokenAuthentication.authenticate", new=mock_token_authenticate
Expand Down
Loading

0 comments on commit ab5805f

Please sign in to comment.