Skip to content

Commit

Permalink
feat(api): retrieve tenant user by id (#2022)
Browse files Browse the repository at this point in the history
  • Loading branch information
nannan00 authored Dec 24, 2024
1 parent 84cd035 commit a089557
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 14 deletions.
16 changes: 16 additions & 0 deletions src/bk-user/bkuser/apis/apigw/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- 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.
57 changes: 57 additions & 0 deletions src/bk-user/bkuser/apis/apigw/authentications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# -*- 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 collections import namedtuple

from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication, get_authorization_header

InnerBearerToken = namedtuple("InnerBearerToken", ["verified"])


class InnerBearerTokenAuthentication(BaseAuthentication):
keyword = "Bearer"

def __init__(self):
self.allowed_tokens = [settings.BK_APIGW_TO_BK_USER_INNER_BEARER_TOKEN]

def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != self.keyword.lower().encode():
return None

if len(auth) == 1:
raise exceptions.AuthenticationFailed("Invalid token header. No credentials provided.")
if len(auth) > 2: # noqa: PLR2004
raise exceptions.AuthenticationFailed("Invalid token header. Token string should not contain spaces.")

try:
token = auth[1].decode()
except UnicodeError:
raise exceptions.AuthenticationFailed(
"Invalid token header. Token string should not contain invalid characters."
)

# Verify Bearer Token
if token not in self.allowed_tokens:
raise exceptions.AuthenticationFailed("Invalid token.")

# Mark Verified Bearer Token
request.inner_bearer_token = InnerBearerToken(verified=True)

return AnonymousUser(), None
22 changes: 22 additions & 0 deletions src/bk-user/bkuser/apis/apigw/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- 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.permissions import BasePermission


class IsInnerBearerTokenAuthenticated(BasePermission):
def has_permission(self, request, view):
return hasattr(request, "inner_bearer_token") and request.inner_bearer_token.verified
25 changes: 25 additions & 0 deletions src/bk-user/bkuser/apis/apigw/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- 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 django.urls import path

from . import views

urlpatterns = [
path(
"tenant-users/<str:tenant_user_id>/", views.TenantUserRetrieveApi.as_view(), name="apigw.tenant_user.retrieve"
),
]
45 changes: 45 additions & 0 deletions src/bk-user/bkuser/apis/apigw/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# -*- 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 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


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

authentication_classes = [InnerBearerTokenAuthentication]
permission_classes = [IsInnerBearerTokenAuthenticated]

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

# [only] 用于减少查询字段,仅查询必要字段
tenant_user = TenantUser.objects.filter(id=tenant_user_id).only("tenant_id").first()
if not tenant_user:
raise error_codes.OBJECT_NOT_FOUND.f(f"user({tenant_user_id}) not found", replace=True)

return Response({"tenant_id": tenant_user.tenant_id})
26 changes: 14 additions & 12 deletions src/bk-user/bkuser/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@
# Static files (CSS, JavaScript, Images)
STATIC_ROOT = BASE_DIR / "staticfiles"
WHITENOISE_STATIC_PREFIX = "/staticfiles/"
# STATIC_URL 也可以是CDN地址
# STATIC_URL 也可以是 CDN 地址
STATIC_URL = env.str("STATIC_URL", SITE_URL + "staticfiles/")
# Media files (excel, pdf, ...)
MEDIA_ROOT = BASE_DIR / "media"

# cookie
SESSION_COOKIE_NAME = "bkuser_sessionid"
SESSION_COOKIE_AGE = 60 * 60 * 24 # 1天
SESSION_COOKIE_AGE = 60 * 60 * 24 # 1 天

# rest_framework
REST_FRAMEWORK = {
Expand Down Expand Up @@ -198,7 +198,7 @@
_BK_USER_IS_SPECIAL_PORT = _BK_USER_URL_PARSE_URL.port in [None, 80, 443]
_BK_USER_SCHEME = _BK_USER_URL_PARSE_URL.scheme
_BK_USER_URL_MD5_16BIT = hashlib.md5(BK_USER_URL.encode("utf-8")).hexdigest()[8:-8]
# 注意:Cookie Domain是不支持端口的
# 注意:Cookie Domain 是不支持端口的
SESSION_COOKIE_DOMAIN = _BK_USER_HOSTNAME
CSRF_COOKIE_DOMAIN = SESSION_COOKIE_DOMAIN
CSRF_COOKIE_NAME = f"bkuser_csrftoken_{_BK_USER_URL_MD5_16BIT}"
Expand All @@ -223,15 +223,17 @@
BK_LOGIN_PLAIN_URL = env.str("BK_LOGIN_PLAIN_URL", default=BK_LOGIN_URL.rstrip("/") + "/plain/")
BK_LOGIN_PLAIN_WINDOW_WIDTH = env.int("BK_LOGIN_PLAIN_WINDOW_WIDTH", default=510)
BK_LOGIN_PLAIN_WINDOW_HEIGHT = env.int("BK_LOGIN_PLAIN_WINDOW_HEIGHT", default=510)
# 登录回调地址参数Key
# 登录回调地址参数 Key
BK_LOGIN_CALLBACK_URL_PARAM_KEY = env.str("BK_LOGIN_CALLBACK_URL_PARAM_KEY", default="c_url")
# 登录API URL
# 登录 API URL
BK_LOGIN_API_URL = env.str("BK_LOGIN_API_URL", default="http://bk-login/login/")

# bk esb api url
BK_COMPONENT_API_URL = env.str("BK_COMPONENT_API_URL")
# bk apigw url tmpl
BK_API_URL_TMPL = env.str("BK_API_URL_TMPL")
# 与网关内部调用的认证 Token
BK_APIGW_TO_BK_USER_INNER_BEARER_TOKEN = env.str("BK_APIGW_TO_BK_USER_INNER_BEARER_TOKEN", default="")

# 版本日志
VERSION_LOG_FILES_DIR = BASE_DIR / "version_log"
Expand All @@ -249,11 +251,11 @@

# 连接 BROKER 超时时间
CELERY_BROKER_CONNECTION_TIMEOUT = 1 # 单位秒
# CELERY 与 RabbitMQ 增加60秒心跳设置项
# CELERY 与 RabbitMQ 增加 60 秒心跳设置项
CELERY_BROKER_HEARTBEAT = 60
# CELERY 并发数,默认为 2,可以通过环境变量或者 Procfile 设置
CELERY_WORKER_CONCURRENCY = env.int("CELERY_WORKER_CONCURRENCY", default=2)
# 与周期任务配置的定时相关UTC
# 与周期任务配置的定时相关 UTC
CELERY_ENABLE_UTC = False
# 任务结果存储
CELERY_RESULT_BACKEND = "django-db"
Expand Down Expand Up @@ -355,7 +357,7 @@
# "SERIALIZER": "django_redis.serializers.pickle.PickleSerializer"
# Redis 连接池配置
"CONNECTION_POOL_KWARGS": {
# redis-py 默认不会关闭连接, 可能会造成连接过多,导致 Redis 无法服务,因此需要设置最大值连接数
# redis-py 默认不会关闭连接可能会造成连接过多,导致 Redis 无法服务,因此需要设置最大值连接数
"max_connections": REDIS_MAX_CONNECTIONS
},
},
Expand Down Expand Up @@ -606,14 +608,14 @@ def _build_file_handler(log_path: Path, filename: str, format: str) -> Dict:
# 是否启用新建租户页面功能
ENABLE_CREATE_TENANT = env.bool("ENABLE_CREATE_TENANT", default=False)

# logo文件大小限制,单位为: KB
# logo 文件大小限制,单位为KB
MAX_LOGO_SIZE = env.int("MAX_LOGO_SIZE", 256)

# 数据源插件默认Logo,值为base64格式图片数据
# 数据源插件默认 Logo,值为 base64 格式图片数据
DEFAULT_DATA_SOURCE_PLUGIN_LOGO = ""
# 租户默认Logo,值为base64格式图片数据
# 租户默认 Logo,值为 base64 格式图片数据
DEFAULT_TENANT_LOGO = ""
# 数据源用户默认Logo,值为base64格式图片数据
# 数据源用户默认 Logo,值为 base64 格式图片数据
DEFAULT_DATA_SOURCE_USER_LOGO = ""
# 默认手机国际区号
DEFAULT_PHONE_COUNTRY_CODE = env.str("DEFAULT_PHONE_COUNTRY_CODE", default="86")
Expand Down
8 changes: 6 additions & 2 deletions src/bk-user/bkuser/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,20 @@
from bkuser.common.views import VueTemplateView

urlpatterns = [
# 产品功能API
# 产品功能 API
path("api/v3/web/", include("bkuser.apis.web.urls")),
# 提供给登录服务使用的内部 API
path("api/v3/login/", include("bkuser.apis.login.urls")),
# 提供给网关使用的内部 API
path("api/v3/apigw/", include("bkuser.apis.apigw.urls")),
# 对外开放的 API
# path("api/v3/open/", include("bkuser.apis.open_v3.urls")),
# 兼容旧版本用户管理 OpenAPI
# Q: 这里使用 api/v1、api/v2 而非 api/v1/open、api/v2/open
# A: 为了保证 ESB 调用的兼容,只需修改 ESB 配置 bk_user host,不需要依赖 ESB 的版本发布
path("api/v1/", include("bkuser.apis.open_v1.urls")),
path("api/v2/", include("bkuser.apis.open_v2.urls")),
# 用于监控相关的,比如ping/healthz/sentry/metrics/otel等等
# 用于监控相关的,比如 ping/healthz/sentry/metrics/otel 等等
path("", include("bkuser.monitoring.urls")),
]

Expand Down
16 changes: 16 additions & 0 deletions src/bk-user/tests/apis/apigw/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- 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.
67 changes: 67 additions & 0 deletions src/bk-user/tests/apis/apigw/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# -*- 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 unittest import mock

import pytest
from bkuser.apis.apigw.authentications import InnerBearerToken
from bkuser.apps.data_source.constants import DataSourceTypeEnum
from bkuser.apps.data_source.models import DataSource
from bkuser.apps.tenant.constants import TenantUserIdRuleEnum
from bkuser.apps.tenant.models import TenantUserIDGenerateConfig
from bkuser.plugins.local.models import LocalDataSourcePluginConfig
from django.contrib.auth.models import AnonymousUser
from rest_framework.test import APIClient

from tests.test_utils.data_source import init_data_source_users_depts_and_relations
from tests.test_utils.tenant import sync_users_depts_to_tenant


def mock_token_authenticate(self, request):
request.inner_bearer_token = InnerBearerToken(verified=True)
return AnonymousUser(), None


@pytest.fixture
def apigw_api_client() -> APIClient:
client = APIClient()

with mock.patch(
"bkuser.apis.apigw.authentications.InnerBearerTokenAuthentication.authenticate", new=mock_token_authenticate
):
yield client


@pytest.fixture
def default_tenant_user_data(default_tenant, local_ds_plugin_cfg, local_ds_plugin) -> DataSource:
"""默认租户的本地数据源数据"""
data_source = DataSource.objects.create(
owner_tenant_id=default_tenant.id,
type=DataSourceTypeEnum.REAL,
plugin=local_ds_plugin,
plugin_config=LocalDataSourcePluginConfig(**local_ds_plugin_cfg),
)
init_data_source_users_depts_and_relations(data_source)

# 设置租户用户生成规则表,让 tenant_user_id 与 数据源 username 一样
TenantUserIDGenerateConfig.objects.create(
data_source=data_source,
target_tenant_id=default_tenant.id,
rule=TenantUserIdRuleEnum.USERNAME,
)

sync_users_depts_to_tenant(default_tenant, data_source)
return data_source
Loading

0 comments on commit a089557

Please sign in to comment.