-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b39162d
commit c91d46f
Showing
10 changed files
with
376 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. | ||
Copyright (C) 2017-2021 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. | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. | ||
Copyright (C) 2017-2021 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. | ||
""" | ||
from rest_framework import serializers | ||
|
||
from bkuser.apps.data_source.models import DataSourceUser | ||
|
||
|
||
class UserCreateInputSLZ(serializers.ModelSerializer): | ||
department_ids = serializers.ListField(help_text="部门", child=serializers.IntegerField(), default=[]) | ||
leader_ids = serializers.ListField(help_text="上级", child=serializers.IntegerField(), default=[]) | ||
|
||
class Meta: | ||
model = DataSourceUser | ||
fields = [ | ||
"username", | ||
"full_name", | ||
"email", | ||
"phone_country_code", | ||
"phone", | ||
"logo", | ||
"department_ids", | ||
"leader_ids", | ||
] | ||
|
||
|
||
class UserCreateOutputSLZ(serializers.Serializer): | ||
id = serializers.CharField(help_text="数据源用户ID") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. | ||
Copyright (C) 2017-2021 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. | ||
""" | ||
from django.urls import path | ||
|
||
from bkuser.apis.web.data_source import views | ||
|
||
urlpatterns = [ | ||
path("<int:id>/users/", views.DataSourceUserListCreateApi.as_view(), name="data_source.list_create"), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. | ||
Copyright (C) 2017-2021 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. | ||
""" | ||
from drf_yasg.utils import swagger_auto_schema | ||
from rest_framework import generics, status | ||
from rest_framework.response import Response | ||
|
||
from bkuser.apis.web.data_source.serializers import UserCreateInputSLZ, UserCreateOutputSLZ | ||
from bkuser.apps.data_source.models import DataSource, DataSourceUser | ||
from bkuser.biz.data_source_organization import ( | ||
DataSourceOrganizationHandler, | ||
DataSourceUserBaseInfo, | ||
DataSourceUserRelationInfo, | ||
) | ||
from bkuser.common.error_codes import error_codes | ||
|
||
|
||
class DataSourceUserListCreateApi(generics.ListCreateAPIView): | ||
pagination_class = None | ||
|
||
@swagger_auto_schema( | ||
operation_description="新建数据源用户", | ||
query_serializer=UserCreateInputSLZ(), | ||
responses={status.HTTP_201_CREATED: UserCreateOutputSLZ(many=True)}, | ||
tags=["data_source"], | ||
) | ||
def post(self, request, *args, **kwargs): | ||
slz = UserCreateInputSLZ(data=request.data) | ||
slz.is_valid(raise_exception=True) | ||
data = slz.validated_data | ||
data_source_id = kwargs["id"] | ||
|
||
# 校验数据源是否存在 | ||
try: | ||
data_source = DataSource.objects.get(id=data_source_id) | ||
except Exception: | ||
raise error_codes.DATA_SOURCE_NOT_EXIST | ||
|
||
# 不允许对非本地数据源进行用户新增操作 | ||
else: | ||
if data_source.plugin.id != "local": | ||
raise error_codes.CANNOT_CREATE_USER | ||
|
||
# 校验是否已存在该用户 | ||
try: | ||
DataSourceUser.objects.get( | ||
username=data["username"], | ||
data_source=data_source, | ||
) | ||
except Exception: | ||
pass | ||
|
||
else: | ||
raise error_codes.DATA_SOURCE_USER_ALREADY_EXISTED | ||
|
||
# 用户数据整合 | ||
base_user_info = DataSourceUserBaseInfo( | ||
data_source=data_source, | ||
username=data["username"], | ||
full_name=data["full_name"], | ||
email=data["email"], | ||
phone=data["phone"], | ||
phone_country_code=data["phone_country_code"], | ||
) | ||
|
||
relation_info = DataSourceUserRelationInfo( | ||
department_ids=data["department_ids"], leader_ids=data["leader_ids"] | ||
) | ||
|
||
user_id = DataSourceOrganizationHandler.create_user( | ||
data_source=data_source, base_user_info=base_user_info, relation_info=relation_info | ||
) | ||
return Response({"id": user_id}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. | ||
Copyright (C) 2017-2021 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. | ||
""" | ||
import logging | ||
import re | ||
|
||
import phonenumbers | ||
from django.utils.translation import gettext_lazy as _ | ||
from phonenumbers import region_code_for_country_code | ||
from rest_framework.exceptions import ValidationError | ||
|
||
USERNAME_REGEX = r"^(\d|[a-zA-Z])([a-zA-Z0-9._-]){0,31}" | ||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def validate_username(value): | ||
if not re.fullmatch(re.compile(USERNAME_REGEX), value): | ||
raise ValidationError(_("{} 不符合 username 命名规范: 由1-32位字母、数字、下划线(_)、点(.)、减号(-)字符组成,以字母或数字开头").format(value)) | ||
|
||
|
||
def validate_phone(phone_country_code: str, phone: str): | ||
try: | ||
# 根据国家码获取对应地区码 | ||
region = region_code_for_country_code(int(phone_country_code)) | ||
|
||
except phonenumbers.NumberParseException: | ||
logger.debug("failed to parse phone_country_code: %s, ", phone_country_code) | ||
|
||
else: | ||
# phonenumbers库在验证号码的时:过短会解析为有效号码,超过250的字节才算超长=》所以这里需要显式做中国号码的长度校验 | ||
if region == "CN" and len(phone) != 11: | ||
raise ValidationError(_("{} 不符合 长度要求").format(phone)) | ||
|
||
try: | ||
# 按照指定地区码解析手机号 | ||
phonenumbers.parse(phone, region) | ||
|
||
except Exception: # pylint: disable=broad-except | ||
logger.debug("failed to parse phone number: %s", phone) | ||
raise ValidationError |
83 changes: 83 additions & 0 deletions
83
src/bk-user/bkuser/apps/data_source_organization/migrations/0001_initial.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# Generated by Django 3.2.20 on 2023-08-10 12:17 | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import django.db.models.manager | ||
import mptt.fields | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='DataSourceUser', | ||
fields=[ | ||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('created_at', models.DateTimeField(auto_now_add=True)), | ||
('updated_at', models.DateTimeField(auto_now=True)), | ||
('data_source_id', models.IntegerField(verbose_name='数据源 ID')), | ||
('username', models.CharField(max_length=128, verbose_name='用户名')), | ||
('full_name', models.CharField(max_length=128, verbose_name='姓名')), | ||
('email', models.EmailField(blank=True, default='', max_length=254, null=True, verbose_name='邮箱')), | ||
('phone', models.CharField(max_length=32, verbose_name='手机号')), | ||
('phone_country_code', models.CharField(blank=True, default='86', max_length=16, null=True, verbose_name='手机国际区号')), | ||
('logo', models.TextField(blank=True, default='', max_length=256, null=True, verbose_name='Logo')), | ||
('extras', models.JSONField(default=dict, verbose_name='自定义字段')), | ||
('leader', models.ManyToManyField(blank=True, related_name='subordinate_staff', to='data_source_organization.DataSourceUser')), | ||
], | ||
options={ | ||
'ordering': ['id'], | ||
'unique_together': {('username', 'data_source_id')}, | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name='LocalDataSourceIdentityInfo', | ||
fields=[ | ||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('created_at', models.DateTimeField(auto_now_add=True)), | ||
('updated_at', models.DateTimeField(auto_now=True)), | ||
('password', models.CharField(blank=True, default='', max_length=255, null=True, verbose_name='用户密码')), | ||
('password_updated_at', models.DateTimeField(blank=True, null=True, verbose_name='密码最后更新时间')), | ||
('password_expired_at', models.DateTimeField(blank=True, null=True, verbose_name='密码过期时间')), | ||
('data_source_id', models.IntegerField(verbose_name='数据源 ID')), | ||
('username', models.CharField(max_length=128, verbose_name='用户名')), | ||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='data_source_organization.datasourceuser')), | ||
], | ||
options={ | ||
'unique_together': {('username', 'data_source_id')}, | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name='DataSourceDepartment', | ||
fields=[ | ||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('created_at', models.DateTimeField(auto_now_add=True)), | ||
('updated_at', models.DateTimeField(auto_now=True)), | ||
('data_source_id', models.IntegerField(verbose_name='数据源 ID')), | ||
('code', models.CharField(blank=True, max_length=128, null=True, verbose_name='部门标识')), | ||
('name', models.CharField(max_length=255, verbose_name='部门名称')), | ||
('extras', models.JSONField(default=dict, verbose_name='自定义字段')), | ||
('order', models.IntegerField(default=1, verbose_name='顺序')), | ||
('lft', models.PositiveIntegerField(editable=False)), | ||
('rght', models.PositiveIntegerField(editable=False)), | ||
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), | ||
('level', models.PositiveIntegerField(editable=False)), | ||
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='data_source_organization.datasourcedepartment')), | ||
('users', models.ManyToManyField(blank=True, related_name='departments', to='data_source_organization.DataSourceUser', verbose_name='成员')), | ||
], | ||
options={ | ||
'verbose_name': '部门表', | ||
'verbose_name_plural': '部门表', | ||
'ordering': ['id'], | ||
'index_together': {('tree_id', 'lft', 'rght'), ('parent_id', 'tree_id', 'lft')}, | ||
}, | ||
managers=[ | ||
('tree_objects', django.db.models.manager.Manager()), | ||
], | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. | ||
Copyright (C) 2017-2021 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. | ||
""" | ||
from typing import List | ||
|
||
from django.db import transaction | ||
from pydantic import BaseModel | ||
|
||
from bkuser.apps.data_source.models import ( | ||
DataSource, | ||
DataSourceDepartmentUserRelation, | ||
DataSourceUser, | ||
DataSourceUserLeaderRelation, | ||
) | ||
from bkuser.apps.tenant.models import Tenant, TenantUser | ||
from bkuser.utils.uuid import generate_uuid | ||
|
||
|
||
class DataSourceUserBaseInfo( | ||
BaseModel, | ||
): | ||
"""数据源用户信息""" | ||
|
||
username: str | ||
full_name: str | ||
email: str | ||
phone: str | ||
phone_country_code: str | ||
|
||
|
||
class DataSourceUserRelationInfo( | ||
BaseModel, | ||
): | ||
"""数据源用户关系信息""" | ||
|
||
department_ids: List | ||
leader_ids: List | ||
|
||
|
||
class DataSourceOrganizationHandler: | ||
@staticmethod | ||
def create_user( | ||
data_source: DataSource, base_user_info: DataSourceUserBaseInfo, relation_info: DataSourceUserRelationInfo | ||
) -> str: | ||
""" | ||
创建数据源用户 | ||
""" | ||
# TODO:补充日志 | ||
with transaction.atomic(): | ||
# 创建数据源用户 | ||
create_user_info_map = {"data_source": data_source, **base_user_info.model_dump()} | ||
user = DataSourceUser.objects.create(**create_user_info_map) | ||
|
||
# 批量创建数据源用户-部门关系 | ||
department_user_relation_objs = [ | ||
DataSourceDepartmentUserRelation(department_id=department_id, user_id=user.id) | ||
for department_id in relation_info.model_dump()["department_ids"] | ||
] | ||
|
||
if department_user_relation_objs: | ||
DataSourceDepartmentUserRelation.objects.bulk_create(department_user_relation_objs, batch_size=100) | ||
|
||
# 批量创建数据源用户-上级关系 | ||
user_leader_relation_objs = [ | ||
DataSourceUserLeaderRelation(leader_id=leader_id, user_id=user.id) | ||
for leader_id in relation_info.model_dump()["leader_ids"] | ||
] | ||
|
||
if user_leader_relation_objs: | ||
DataSourceUserLeaderRelation.objects.bulk_create(user_leader_relation_objs) | ||
|
||
# 查询关联的租户 | ||
tenant = Tenant.objects.get(id=data_source.owner_tenant_id) | ||
# 创建租户用户 | ||
TenantUser.objects.create( | ||
data_source_user=user, | ||
tenant=tenant, | ||
data_source=data_source, | ||
id=generate_uuid(), | ||
) | ||
|
||
return user.id |
Oops, something went wrong.