mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-08-09 16:11:22 +00:00
feat: add configurable expiration notice settings
This commit is contained in:
@@ -16,6 +16,20 @@ def local_now():
|
||||
return dj_timezone.localtime(dj_timezone.now())
|
||||
|
||||
|
||||
def expiration_remain_days(date_expired, today=None):
|
||||
if today is None:
|
||||
today = local_now().date()
|
||||
date_expired = dj_timezone.localtime(date_expired).date()
|
||||
return (date_expired - today).days
|
||||
|
||||
|
||||
def should_notify_expiration(remain_days, first_notice_days, daily_notice_days):
|
||||
return (
|
||||
remain_days == first_notice_days
|
||||
or 0 <= remain_days <= daily_notice_days
|
||||
)
|
||||
|
||||
|
||||
def local_now_display(fmt='%Y-%m-%d %H:%M:%S'):
|
||||
return local_now().strftime(fmt)
|
||||
|
||||
|
||||
@@ -1860,5 +1860,13 @@
|
||||
"LabelFilterAllValues": "全部值",
|
||||
"LabelFilterSelectKey": "请选择标签键",
|
||||
"LabelFilterKeyLabel": "键",
|
||||
"LabelFilterValueLabel": "值"
|
||||
"LabelFilterValueLabel": "值",
|
||||
"NotificationSettings": "通知设置",
|
||||
"FirstNotice": "首次提醒",
|
||||
"DailyNotice": "每日提醒",
|
||||
"NoticeAdvance": "提前",
|
||||
"NoticeDay": "天",
|
||||
"NoticeStart": "开始",
|
||||
"PositiveIntegerRequired": "请输入正整数。",
|
||||
"FirstNoticeNotLessThanDailyNotice": "首次提醒天数必须大于或等于每日提醒天数。"
|
||||
}
|
||||
|
||||
@@ -676,6 +676,10 @@ class Config(dict):
|
||||
|
||||
'PERM_EXPIRED_CHECK_PERIODIC': 60 * 60,
|
||||
'PERM_EXPIRED_NOTICE_DAYS': 3,
|
||||
'USER_EXPIRED_FIRST_NOTICE_DAYS': 5,
|
||||
'USER_EXPIRED_DAILY_NOTICE_DAYS': 5,
|
||||
'PERM_EXPIRED_FIRST_NOTICE_DAYS': 3,
|
||||
'PERM_EXPIRED_DAILY_NOTICE_DAYS': 3,
|
||||
'PERM_TREE_REGEN_INTERVAL': 1,
|
||||
'FLOWER_URL': "127.0.0.1:5555",
|
||||
'LANGUAGE_CODE': 'en',
|
||||
@@ -812,6 +816,8 @@ class Config(dict):
|
||||
old_config_map = {
|
||||
'CONNECTION_TOKEN_ONETIME_EXPIRATION': 'CONNECTION_TOKEN_EXPIRATION',
|
||||
'CONNECTION_TOKEN_REUSABLE_EXPIRATION': 'CONNECTION_TOKEN_EXPIRATION_MAX',
|
||||
'PERM_EXPIRED_FIRST_NOTICE_DAYS': 'PERM_EXPIRED_NOTICE_DAYS',
|
||||
'PERM_EXPIRED_DAILY_NOTICE_DAYS': 'PERM_EXPIRED_NOTICE_DAYS',
|
||||
}
|
||||
|
||||
def __init__(self, *args):
|
||||
|
||||
@@ -111,6 +111,10 @@ TICKET_AUTHORIZE_DEFAULT_TIME = CONFIG.TICKET_AUTHORIZE_DEFAULT_TIME
|
||||
TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = CONFIG.TICKET_AUTHORIZE_DEFAULT_TIME_UNIT
|
||||
PERM_EXPIRED_CHECK_PERIODIC = CONFIG.PERM_EXPIRED_CHECK_PERIODIC
|
||||
PERM_EXPIRED_NOTICE_DAYS = CONFIG.PERM_EXPIRED_NOTICE_DAYS
|
||||
USER_EXPIRED_FIRST_NOTICE_DAYS = CONFIG.USER_EXPIRED_FIRST_NOTICE_DAYS
|
||||
USER_EXPIRED_DAILY_NOTICE_DAYS = CONFIG.USER_EXPIRED_DAILY_NOTICE_DAYS
|
||||
PERM_EXPIRED_FIRST_NOTICE_DAYS = CONFIG.PERM_EXPIRED_FIRST_NOTICE_DAYS
|
||||
PERM_EXPIRED_DAILY_NOTICE_DAYS = CONFIG.PERM_EXPIRED_DAILY_NOTICE_DAYS
|
||||
FLOWER_URL = CONFIG.FLOWER_URL
|
||||
|
||||
# Enable internal period task
|
||||
|
||||
@@ -11,7 +11,9 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.const.crontab import CRONTAB_AT_AM_TEN
|
||||
from common.utils import get_logger
|
||||
from common.utils.timezone import local_now, dt_parser
|
||||
from common.utils.timezone import (
|
||||
dt_parser, expiration_remain_days, local_now, should_notify_expiration,
|
||||
)
|
||||
from ops.celery.decorator import register_as_period_task
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from perms.models import AssetPermission
|
||||
@@ -58,8 +60,10 @@ def check_asset_permission_expired():
|
||||
@atomic()
|
||||
@tmp_to_root_org()
|
||||
def check_asset_permission_will_expired():
|
||||
start = local_now()
|
||||
end = start + timedelta(days=settings.PERM_EXPIRED_NOTICE_DAYS)
|
||||
first_notice_days = settings.PERM_EXPIRED_FIRST_NOTICE_DAYS
|
||||
daily_notice_days = settings.PERM_EXPIRED_DAILY_NOTICE_DAYS
|
||||
start = local_now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end = start + timedelta(days=max(first_notice_days, daily_notice_days) + 1)
|
||||
|
||||
user_asset_remain_day_mapper = defaultdict(dict)
|
||||
org_perm_remain_day_mapper = defaultdict(dict)
|
||||
@@ -72,7 +76,10 @@ def check_asset_permission_will_expired():
|
||||
|
||||
for asset_perm in asset_perms:
|
||||
date_expired = dt_parser(asset_perm.date_expired)
|
||||
remain_days = (date_expired - start).days
|
||||
remain_days = expiration_remain_days(date_expired, start.date())
|
||||
if not should_notify_expiration(
|
||||
remain_days, first_notice_days, daily_notice_days):
|
||||
continue
|
||||
|
||||
org = asset_perm.org
|
||||
# 资产授权按照组织分类
|
||||
|
||||
@@ -73,6 +73,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
|
||||
'ops': serializers.OpsSettingSerializer,
|
||||
'virtualapp': serializers.VirtualAppSerializer,
|
||||
'tool': serializers.ToolSerializer,
|
||||
'task_notice': serializers.TaskNoticeSettingSerializer,
|
||||
}
|
||||
|
||||
rbac_category_permissions = {
|
||||
@@ -89,6 +90,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
|
||||
'security_session': 'settings.change_security',
|
||||
'security_password': 'settings.change_security',
|
||||
'security_login_limit': 'settings.change_security',
|
||||
'task_notice': 'settings.change_security',
|
||||
'ldap': 'settings.change_auth',
|
||||
'ldap_ha': 'settings.change_auth',
|
||||
'cas': 'settings.change_auth',
|
||||
|
||||
@@ -12,5 +12,6 @@ from .prompt import *
|
||||
from .public import *
|
||||
from .security import *
|
||||
from .settings import *
|
||||
from .task import *
|
||||
from .terminal import *
|
||||
from .tool import *
|
||||
|
||||
@@ -17,6 +17,7 @@ from .cleaning import CleaningSerializer
|
||||
from .msg import EmailSettingSerializer, EmailContentSettingSerializer
|
||||
from .other import OtherSettingSerializer
|
||||
from .security import SecuritySettingSerializer
|
||||
from .task import TaskNoticeSettingSerializer
|
||||
from .terminal import TerminalSettingSerializer
|
||||
|
||||
__all__ = [
|
||||
@@ -90,7 +91,8 @@ class SettingsSerializer(
|
||||
TencentSMSSettingSerializer,
|
||||
CMPP2SMSSettingSerializer,
|
||||
CustomSMSSettingSerializer,
|
||||
PasskeySettingSerializer
|
||||
PasskeySettingSerializer,
|
||||
TaskNoticeSettingSerializer,
|
||||
):
|
||||
PREFIX_TITLE = _('Setting')
|
||||
CACHE_KEY = 'SETTING_FIELDS_MAPPING'
|
||||
|
||||
36
apps/settings/serializers/task.py
Normal file
36
apps/settings/serializers/task.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
__all__ = ['TaskNoticeSettingSerializer']
|
||||
|
||||
|
||||
class TaskNoticeSettingSerializer(serializers.Serializer):
|
||||
USER_EXPIRED_FIRST_NOTICE_DAYS = serializers.IntegerField(
|
||||
min_value=1, max_value=3650, label=_('First notice'),
|
||||
)
|
||||
USER_EXPIRED_DAILY_NOTICE_DAYS = serializers.IntegerField(
|
||||
min_value=1, max_value=365, label=_('Daily notice'),
|
||||
)
|
||||
PERM_EXPIRED_FIRST_NOTICE_DAYS = serializers.IntegerField(
|
||||
min_value=1, max_value=3650, label=_('First notice'),
|
||||
)
|
||||
PERM_EXPIRED_DAILY_NOTICE_DAYS = serializers.IntegerField(
|
||||
min_value=1, max_value=365, label=_('Daily notice'),
|
||||
)
|
||||
|
||||
notice_field_pairs = (
|
||||
('USER_EXPIRED_FIRST_NOTICE_DAYS', 'USER_EXPIRED_DAILY_NOTICE_DAYS'),
|
||||
('PERM_EXPIRED_FIRST_NOTICE_DAYS', 'PERM_EXPIRED_DAILY_NOTICE_DAYS'),
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
for first_name, daily_name in self.notice_field_pairs:
|
||||
if first_name not in attrs and daily_name not in attrs:
|
||||
continue
|
||||
first_days = attrs.get(first_name, getattr(settings, first_name))
|
||||
daily_days = attrs.get(daily_name, getattr(settings, daily_name))
|
||||
if first_days < daily_days:
|
||||
message = _('First notice days must be greater than or equal to daily notice days.')
|
||||
raise serializers.ValidationError({first_name: message})
|
||||
return attrs
|
||||
@@ -12,6 +12,9 @@ from django.utils.translation import gettext_lazy as _, gettext_noop
|
||||
from audits.const import ActivityChoices
|
||||
from common.const.crontab import CRONTAB_AT_AM_TEN, CRONTAB_AT_PM_TWO
|
||||
from common.utils import get_logger
|
||||
from common.utils.timezone import (
|
||||
expiration_remain_days, local_now, should_notify_expiration,
|
||||
)
|
||||
from ops.celery.decorator import after_app_ready_start, register_as_period_task
|
||||
from ops.celery.utils import create_or_update_celery_periodic_tasks
|
||||
from orgs.utils import tmp_to_root_org
|
||||
@@ -71,18 +74,23 @@ def check_password_expired_periodic():
|
||||
)
|
||||
)
|
||||
def check_user_expired():
|
||||
date_expired_lt = timezone.now() + timezone.timedelta(days=User.DATE_EXPIRED_WARNING_DAYS)
|
||||
first_notice_days = settings.USER_EXPIRED_FIRST_NOTICE_DAYS
|
||||
daily_notice_days = settings.USER_EXPIRED_DAILY_NOTICE_DAYS
|
||||
start = local_now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end = start + timedelta(days=max(first_notice_days, daily_notice_days) + 1)
|
||||
users = User.get_nature_users() \
|
||||
.filter(source=User.Source.local) \
|
||||
.filter(date_expired__lt=date_expired_lt)
|
||||
.filter(date_expired__gte=start, date_expired__lt=end)
|
||||
|
||||
for user in users:
|
||||
if not user.is_valid:
|
||||
if not user.is_active:
|
||||
continue
|
||||
if not user.will_expired:
|
||||
remain_days = expiration_remain_days(user.date_expired, start.date())
|
||||
if not should_notify_expiration(
|
||||
remain_days, first_notice_days, daily_notice_days):
|
||||
continue
|
||||
msg = "The user {} will expires in {} days"
|
||||
logger.info(msg.format(user, user.expired_remain_days))
|
||||
logger.info(msg.format(user, remain_days))
|
||||
UserExpirationReminderMsg(user).publish_async()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user