Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: 新增配置- 账户有效期 #1361

Merged
merged 28 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
112fabc
feat: 新增配置- 账户有效期
neronkl Nov 2, 2023
2ca445d
feat: 新增租户用户,根据配置,设置账户有效期
neronkl Nov 2, 2023
4fc127a
refactor: cr 调整
neronkl Nov 3, 2023
872ce1a
refactor: cr 调整
neronkl Nov 3, 2023
4b96497
feat: 通知过期/临期用户,注册定时任务
neronkl Nov 6, 2023
f9c1e00
fix: work out code conflict
neronkl Nov 6, 2023
c0c7602
refactor: cr调整
neronkl Nov 7, 2023
abb1e07
refactor: cr调整
neronkl Nov 7, 2023
c401079
refactor: cr调整
neronkl Nov 7, 2023
65de189
refactor: cr调整
neronkl Nov 7, 2023
6791a94
refactor: cr调整
neronkl Nov 8, 2023
b7a317b
refactor: cr调整
neronkl Nov 8, 2023
7c31c37
refactor: cr调整
neronkl Nov 9, 2023
0a2aac7
refactor: cr调整
neronkl Nov 9, 2023
383f286
refactor: cr调整
neronkl Nov 9, 2023
6a6db2c
refactor: cr调整
neronkl Nov 10, 2023
e525b6b
refactor: cr调整
neronkl Nov 10, 2023
777957e
refactor: cr调整
neronkl Nov 10, 2023
7b954d1
refactor: cr调整
neronkl Nov 10, 2023
13f3739
refactor: add migrations file
neronkl Nov 10, 2023
c30cfe9
refactor: cr 调整
neronkl Nov 11, 2023
5c2de7e
refactor: cr 调整
neronkl Nov 13, 2023
f5c097c
refactor: 添加注释
neronkl Nov 13, 2023
0e51bae
refactor: cr调整
neronkl Nov 13, 2023
aa7861b
refactor: cr调整
neronkl Nov 13, 2023
bfd9e53
refactor: cr调整
neronkl Nov 13, 2023
7bb5157
Merge branch 'ft_tenant' of github.com:TencentBlueKing/bk-user into f…
neronkl Nov 13, 2023
a33d5ed
feature: add migrations
neronkl Nov 13, 2023
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
8 changes: 8 additions & 0 deletions src/bk-user/bkuser/apis/web/tenant/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
TenantHandler,
TenantManagerWithoutID,
)
from bkuser.biz.tenant_setting import TenantUserValidityPeriodConfigHandler
from bkuser.common.views import ExcludePatchAPIViewMixin
from bkuser.plugins.local.models import PasswordInitialConfig

Expand Down Expand Up @@ -99,8 +100,15 @@ def post(self, request, *args, **kwargs):
]
# 本地数据源密码初始化配置
config = PasswordInitialConfig(**data["password_initial_config"])

# 创建租户和租户管理员
tenant_id = TenantHandler.create_with_managers(tenant_info, managers, config)

# 创建租户完成后,初始化账号有效期设置
TenantUserValidityPeriodConfigHandler().init_tenant_user_validity_period_config(
tenant_id, request.user.username
)

return Response(TenantCreateOutputSLZ(instance={"id": tenant_id}).data)


Expand Down
29 changes: 26 additions & 3 deletions src/bk-user/bkuser/apis/web/tenant_setting/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class TenantUserCustomFieldCreateInputSLZ(serializers.Serializer):
data_type = serializers.ChoiceField(help_text="字段类型", choices=UserFieldDataType.get_choices())
required = serializers.BooleanField(help_text="是否必填")
default = serializers.JSONField(help_text="默认值", required=False)
options = serializers.JSONField(help_text="选项", required=False, default=list)
options = serializers.JSONField(help_text="选项", required=False)

def validate_display_name(self, display_name):
if TenantUserCustomField.objects.filter(
Expand Down Expand Up @@ -131,8 +131,8 @@ class TenantUserCustomFieldCreateOutputSLZ(serializers.Serializer):
class TenantUserCustomFieldUpdateInputSLZ(serializers.Serializer):
display_name = serializers.CharField(help_text="展示用名称", max_length=128)
required = serializers.BooleanField(help_text="是否必填")
default = serializers.JSONField(help_text="默认值", required=False)
options = serializers.JSONField(help_text="选项", required=False, default=list)
default = serializers.JSONField(help_text="默认值")
options = serializers.JSONField(help_text="选项")
narasux marked this conversation as resolved.
Show resolved Hide resolved

def validate_display_name(self, display_name):
if (
Expand Down Expand Up @@ -163,3 +163,26 @@ def validate(self, attrs):
_validate_multi_enum_default(default, opt_ids)

return attrs


class NotificationTemplatesInputSLZ(serializers.Serializer):
method = serializers.CharField(help_text="信息传递方式")
narasux marked this conversation as resolved.
Show resolved Hide resolved
scene = serializers.CharField(help_text="通知类型")
title = serializers.CharField(help_text="通知标题", allow_null=True)
sender = serializers.CharField(help_text="发送人")
content = serializers.CharField(help_text="通知内容")
content_html = serializers.CharField(help_text="通知内容,页面展示使用")


class TenantUserValidityPeriodConfigInputSLZ(serializers.Serializer):
enabled_validity_period = serializers.BooleanField(help_text="账户有效期使能值")
narasux marked this conversation as resolved.
Show resolved Hide resolved
valid_time = serializers.IntegerField(help_text="账户有效期")
remind_before_expire = serializers.ListField(help_text="临过期提醒时间")
enabled_notification_methods = serializers.ListField(help_text="通知方式")
nannan00 marked this conversation as resolved.
Show resolved Hide resolved
notification_templates = serializers.ListField(
help_text="通知模板", child=NotificationTemplatesInputSLZ(), allow_empty=False
)


class TenantUserValidityPeriodConfigOutputSLZ(TenantUserValidityPeriodConfigInputSLZ):
pass
5 changes: 5 additions & 0 deletions src/bk-user/bkuser/apis/web/tenant_setting/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@
views.TenantUserCustomFieldUpdateDeleteApi.as_view(),
name="tenant_setting_custom_fields.update_delete",
),
path(
"settings/tenant-user-validity-period/",
views.TenantUserValidityPeriodConfigRetrieveUpdateApi.as_view(),
name="tenant_user_validity_period_config.retrieve_update",
),
]
77 changes: 73 additions & 4 deletions src/bk-user/bkuser/apis/web/tenant_setting/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema
from rest_framework import generics, status
from rest_framework.response import Response
Expand All @@ -18,9 +19,18 @@
TenantUserCustomFieldCreateOutputSLZ,
TenantUserCustomFieldUpdateInputSLZ,
TenantUserFieldOutputSLZ,
TenantUserValidityPeriodConfigInputSLZ,
TenantUserValidityPeriodConfigOutputSLZ,
)
from bkuser.apps.tenant.models import TenantUserCustomField, UserBuiltinField
from bkuser.common.views import ExcludePutAPIViewMixin
from bkuser.apps.tenant.models import (
TenantManager,
TenantUserCustomField,
TenantUserValidityPeriodConfig,
UserBuiltinField,
)
from bkuser.biz.tenant_setting import NotificationTemplate, TenantUserValidityPeriodConfigHandler, ValidityPeriodConfig
from bkuser.common.error_codes import error_codes
from bkuser.common.views import ExcludePatchAPIViewMixin, ExcludePutAPIViewMixin


class TenantUserFieldListApi(CurrentUserTenantMixin, generics.ListAPIView):
Expand Down Expand Up @@ -82,8 +92,7 @@ def put(self, request, *args, **kwargs):
tenant_id = self.get_current_tenant_id()

slz = TenantUserCustomFieldUpdateInputSLZ(
data=request.data,
context={"tenant_id": tenant_id, "current_custom_field_id": kwargs["id"]},
data=request.data, context={"tenant_id": tenant_id, "current_custom_field_id": kwargs["id"]}
)
slz.is_valid(raise_exception=True)
data = slz.validated_data
Expand All @@ -104,3 +113,63 @@ def put(self, request, *args, **kwargs):
)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)


class TenantUserValidityPeriodConfigRetrieveUpdateApi(
ExcludePatchAPIViewMixin, CurrentUserTenantMixin, generics.RetrieveUpdateAPIView
):
queryset = TenantUserValidityPeriodConfig.objects.all()

def get_object(self) -> TenantUserValidityPeriodConfig:
tenant_id = self.get_current_tenant_id()
account_validity_period_config = self.queryset.filter(tenant_id=tenant_id).first()
neronkl marked this conversation as resolved.
Show resolved Hide resolved
if not account_validity_period_config:
raise error_codes.OBJECT_NOT_FOUND.f(_("账户有效期配置丢失,请联系系统管理员"))

return account_validity_period_config

@swagger_auto_schema(
tags=["tenant-setting"],
operation_description="当前租户的账户有效期配置",
responses={
status.HTTP_200_OK: TenantUserValidityPeriodConfigOutputSLZ(),
},
)
def get(self, request, *args, **kwargs):
instance = self.get_object()
slz = TenantUserValidityPeriodConfigOutputSLZ(instance)
return Response(slz.data)
nannan00 marked this conversation as resolved.
Show resolved Hide resolved

@swagger_auto_schema(
tags=["tenant-setting"],
operation_description="更新当前租户的账户有效期配置",
request_body=TenantUserValidityPeriodConfigInputSLZ(),
responses={
status.HTTP_200_OK: "",
},
)
def put(self, request, *args, **kwargs):
tenant_id = self.get_current_tenant_id()

# 边界限制: 当前租户的管理才可做更新操作
narasux marked this conversation as resolved.
Show resolved Hide resolved
operator = request.user.username
if not TenantManager.objects.filter(tenant_id=tenant_id, tenant_user_id=operator).exists():
raise error_codes.NO_PERMISSION

input_slz = TenantUserValidityPeriodConfigInputSLZ(data=request.data)
narasux marked this conversation as resolved.
Show resolved Hide resolved
input_slz.is_valid(raise_exception=True)

data = input_slz.validated_data
tenant_user_validity_period_config = ValidityPeriodConfig(
enabled_validity_period=data["enabled_validity_period"],
valid_time=data["valid_time"],
remind_before_expire=data["remind_before_expire"],
enabled_notification_methods=data["enabled_notification_methods"],
notification_templates=[NotificationTemplate(**item) for item in data["notification_templates"]],
neronkl marked this conversation as resolved.
Show resolved Hide resolved
)

TenantUserValidityPeriodConfigHandler.update_tenant_user_validity_period_config(
tenant_id, operator, tenant_user_validity_period_config
)

return Response()
narasux marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 9 additions & 3 deletions src/bk-user/bkuser/apps/sync/syncers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from bkuser.apps.sync.constants import DataSourceSyncObjectType, SyncOperation, TenantSyncObjectType
from bkuser.apps.sync.context import DataSourceSyncTaskContext, TenantSyncTaskContext
from bkuser.apps.sync.converters import DataSourceUserConverter
from bkuser.apps.tenant.models import Tenant, TenantDepartment, TenantUser
from bkuser.apps.tenant.models import Tenant, TenantDepartment, TenantUser, TenantUserValidityPeriodConfig
from bkuser.common.constants import PERMANENT_TIME
from bkuser.plugins.models import RawDataSourceDepartment, RawDataSourceUser
from bkuser.utils.tree import bfs_traversal_tree, build_forest_with_parent_relations
Expand Down Expand Up @@ -515,6 +515,7 @@ def sync(self):
waiting_sync_data_source_users = data_source_users.exclude(
id__in=[u.data_source_user_id for u in exists_tenant_users]
)

narasux marked this conversation as resolved.
Show resolved Hide resolved
waiting_create_tenant_users = [
TenantUser(
id=generate_uuid(),
Expand All @@ -532,5 +533,10 @@ def sync(self):
self.ctx.recorder.add(SyncOperation.CREATE, TenantSyncObjectType.USER, waiting_create_tenant_users)

def _get_user_account_expired_at(self) -> datetime.datetime:
"""FIXME (su) 支持读取账号有效期配置,然后累加到 timezone.now() 上,目前是直接返回 PERMANENT_TIME"""
narasux marked this conversation as resolved.
Show resolved Hide resolved
return PERMANENT_TIME
# 根据配置初始化账号有效期
valid_time = PERMANENT_TIME
validity_period_config = TenantUserValidityPeriodConfig.objects.get(tenant_id=self.tenant.id)
narasux marked this conversation as resolved.
Show resolved Hide resolved
if validity_period_config.enabled_validity_period and validity_period_config.valid_time > 0:
valid_time = timezone.now() + datetime.timedelta(days=validity_period_config.valid_time)

return valid_time
14 changes: 14 additions & 0 deletions src/bk-user/bkuser/apps/tenant/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,17 @@ class UserFieldDataType(str, StructuredEnum):
NUMBER = EnumField("number", label=_("数字"))
ENUM = EnumField("enum", label=_("枚举"))
MULTI_ENUM = EnumField("multi_enum", label=_("多选枚举"))


class NotificationMethod(str, StructuredEnum):
"""通知方式"""

EMAIL = EnumField("email", label=_("邮件通知"))
SMS = EnumField("sms", label=_("短信通知"))


class NotificationScene(str, StructuredEnum):
"""通知场景"""

TENANT_USER_EXPIRING = EnumField("tenant_user_expiring", label=_("临过期提醒"))
TENANT_USER_EXPIRED = EnumField("tenant_user_expired", label=_("过期提醒"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 3.2.20 on 2023-11-01 08:53

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('tenant', '0005_builtin_user_fields'),
]

operations = [
migrations.CreateModel(
name='TenantUserValidityPeriodConfig',
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)),
('creator', models.CharField(blank=True, max_length=128, null=True)),
('updater', models.CharField(blank=True, max_length=128, null=True)),
('enabled_validity_period', models.BooleanField(default=True, verbose_name='是否启用')),
('valid_time', models.IntegerField(default=-1, verbose_name='有效期(单位:天)')),
('remind_before_expire', models.JSONField(default=list, verbose_name='临X天过期发送提醒(单位:天)')),
('enabled_notification_methods', models.JSONField(default=dict, verbose_name='通知方式')),
('notification_templates', models.JSONField(default=dict, verbose_name='通知模板')),
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tenant.tenant', unique=True)),
],
options={
'abstract': False,
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.20 on 2023-11-06 04:10

import datetime
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('tenant', '0006_tenantuservalidityperiodconfig'),
]

operations = [
migrations.AlterField(
model_name='tenantuser',
name='account_expired_at',
field=models.DateField(blank=True, default=datetime.datetime(2100, 1, 1, 0, 0), null=True, verbose_name='账号过期时间'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 3.2.20 on 2023-11-06 04:10

import datetime

import pytz
from django.db import migrations, models
from django_celery_beat.models import CrontabSchedule, PeriodicTask


def generate_periodic_tasks(apps, schema_editor):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

而且半夜 12 点给用户发送通知?是否合理?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

和产品确认,两种通知都在10点发出

crontab, _ = CrontabSchedule.objects.update_or_create(
minute="0",
hour="0",
day_of_week="*",
day_of_month='*',
month_of_year='*',
timezone=pytz.timezone("Asia/Shanghai"),
)
for periodic_task_name in ["send_tenant_user_expiring_notification", "send_tenant_user_expired_notification"]:
PeriodicTask.objects.get_or_create(
name=periodic_task_name,
defaults={
"crontab": crontab,
"task": f"bkuser.apps.tenant.periodic_tasks.{periodic_task_name}",
},
)


class Migration(migrations.Migration):

dependencies = [
('tenant', '0007_alter_tenantuser_account_expired_at'),
]

operations = [migrations.RunPython(generate_periodic_tasks)]
34 changes: 15 additions & 19 deletions src/bk-user/bkuser/apps/tenant/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
"""
from django.conf import settings
from django.db import models
from django.utils import timezone

from bkuser.apps.data_source.models import DataSource, DataSourceDepartment, DataSourceUser
from bkuser.apps.tenant.constants import TenantFeatureFlag, UserFieldDataType
from bkuser.common.constants import PERMANENT_TIME, BkLanguageEnum
from bkuser.common.models import TimestampedModel
from bkuser.common.time import datetime_to_display
from bkuser.common.models import AuditedModel, TimestampedModel

from .constants import TIME_ZONE_CHOICES

Expand Down Expand Up @@ -59,7 +59,7 @@ class TenantUser(TimestampedModel):
wx_openid = models.CharField("微信公众号OpenID", null=True, blank=True, default="", max_length=64)
narasux marked this conversation as resolved.
Show resolved Hide resolved

# 账号有效期相关
account_expired_at = models.DateTimeField("账号过期时间", null=True, blank=True, default=PERMANENT_TIME)
account_expired_at = models.DateField("账号过期时间", null=True, blank=True, default=PERMANENT_TIME)
narasux marked this conversation as resolved.
Show resolved Hide resolved

# 手机&邮箱相关:手机号&邮箱都可以继承数据源或自定义
is_inherited_phone = models.BooleanField("是否继承数据源手机号", default=True)
Expand All @@ -81,7 +81,8 @@ class Meta:

@property
def account_expired_at_display(self) -> str:
return datetime_to_display(self.account_expired_at)
local_time = timezone.localtime(self.account_expired_at)
return local_time.strftime("%Y-%m-%d %H:%M:%S")
neronkl marked this conversation as resolved.
Show resolved Hide resolved


class TenantDepartment(TimestampedModel):
Expand Down Expand Up @@ -143,21 +144,16 @@ class Meta:
]


# # TODO: 是否直接定义 TenantCommonConfig 表,AccountValidityPeriod是一个JSON字段?
# class AccountValidityPeriodConfig:
# """账号时效配置"""
#
# tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, db_index=True, unique=True)
#
# enabled = models.BooleanField("是否启用", default=True)
# # TODO: 定义枚举,设置默认值为永久
# validity_period_seconds = models.IntegerField("有效期(单位:秒)", default=-1)
# # TODO: 定义枚举,设置默认值为7天
# reminder_period_days = models.IntegerField("提醒周期(单位:天)", default=7)
# # TODO: 定义枚举,同时需要考虑到与企业ESB配置的支持的通知方式有关,是否定义字段?
# notification_method = models.CharField("通知方式", max_length=32, default="email")
# # TODO: 需要考虑不同通知方式,可能无法使用相同模板,或者其他设计方式
# notification_content_template = models.TextField("通知模板", default="")
class TenantUserValidityPeriodConfig(AuditedModel):
narasux marked this conversation as resolved.
Show resolved Hide resolved
"""账号有效期-配置"""
narasux marked this conversation as resolved.
Show resolved Hide resolved

tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, db_index=True, unique=True)
narasux marked this conversation as resolved.
Show resolved Hide resolved

enabled_validity_period = models.BooleanField("是否启用", default=True)
valid_time = models.IntegerField("有效期(单位:天)", default=-1)
narasux marked this conversation as resolved.
Show resolved Hide resolved
remind_before_expire = models.JSONField("临X天过期发送提醒(单位:天)", default=list)
enabled_notification_methods = models.JSONField("通知方式", default=dict)
notification_templates = models.JSONField("通知模板", default=dict)
narasux marked this conversation as resolved.
Show resolved Hide resolved


# class TenantUserSocialAccountRelation(TimestampedModel):
Expand Down
Loading
Loading