perf: 优化消息通知 (#7024)

* perf: 优化系统用户列表

* stash

* perf: 优化消息通知

* perf: 修改钉钉

* perf: 修改优化消息通知

* perf: 修改requirements

* perf: 优化datetime

Co-authored-by: ibuler <ibuler@qq.com>
pull/7033/head
fit2bot 2021-10-20 19:45:37 +08:00 committed by GitHub
parent 9acfd461b4
commit 00d434ceea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 948 additions and 1738 deletions

View File

@ -93,8 +93,6 @@ class LoginACL(BaseACL):
return allow, reject_type
@staticmethod
def construct_confirm_ticket_meta(request=None):
login_ip = get_request_ip(request) if request else ''

View File

@ -187,7 +187,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.annotate(assets_amount=Count("assets"))
queryset = queryset\
.annotate(assets_amount=Count("assets"))\
.prefetch_related('nodes', 'cmd_filters')
return queryset

View File

@ -9,7 +9,7 @@ from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.permissions import AllowAny
from common.utils.timezone import utcnow
from common.utils.timezone import utc_now
from common.const.http import POST, GET
from common.drf.api import JMSGenericViewSet
from common.drf.serializers import EmptySerializer
@ -79,7 +79,7 @@ class SSOViewSet(AuthMixin, JMSGenericViewSet):
return HttpResponseRedirect(next_url)
# 判断是否过期
if (utcnow().timestamp() - token.date_created.timestamp()) > settings.AUTH_SSO_AUTHKEY_TTL:
if (utc_now().timestamp() - token.date_created.timestamp()) > settings.AUTH_SSO_AUTHKEY_TTL:
self.send_auth_signal(success=False, reason='authkey_timeout')
return HttpResponseRedirect(next_url)

View File

@ -6,5 +6,6 @@ class AuthenticationConfig(AppConfig):
def ready(self):
from . import signals_handlers
from . import notifications
super().ready()

View File

@ -1,25 +1,12 @@
from django.utils import timezone
from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
from notifications.notifications import UserMessage
from settings.api import PublicSettingApi
from common.utils import get_logger
logger = get_logger(__file__)
EMAIL_TEMPLATE = _(
""
"<h3>{subject}</h3>"
"<p>Dear {server_name} user, Hello!</p>"
"<p>Your account has remote login behavior, please pay attention.</p>"
"<p>User: {username}</p>"
"<p>Login time: {time}</p>"
"<p>Login location: {city} ({ip})</p>"
"<p>If you suspect that the login behavior is abnormal, please modify "
"<p>the account password in time.</p>"
"<br>"
"<p>Thank you for your attention to {server_name}!</p>")
class DifferentCityLoginMessage(UserMessage):
def __init__(self, user, ip, city):
@ -27,49 +14,28 @@ class DifferentCityLoginMessage(UserMessage):
self.city = city
super().__init__(user)
@property
def time(self):
return timezone.now().strftime("%Y-%m-%d %H:%M:%S")
@property
def subject(self):
return _('Different city login reminder')
def get_text_msg(self) -> dict:
message = _(
""
"{subject}\n"
"Dear {server_name} user, Hello!\n"
"Your account has remote login behavior, please pay attention.\n"
"User: {username}\n"
"Login time: {time}\n"
"Login location: {city} ({ip})\n"
"If you suspect that the login behavior is abnormal, please modify "
"the account password in time.\n"
"Thank you for your attention to {server_name}!\n"
).format(
subject=self.subject,
server_name=PublicSettingApi.get_login_title(),
username=self.user.username,
ip=self.ip,
time=self.time,
city=self.city,
)
return {
'subject': self.subject,
'message': message
}
def get_html_msg(self) -> dict:
message = EMAIL_TEMPLATE.format(
subject=self.subject,
server_name=PublicSettingApi.get_login_title(),
now_local = timezone.localtime(timezone.now())
now = now_local.strftime("%Y-%m-%d %H:%M:%S")
subject = _('Different city login reminder')
context = dict(
subject=subject,
name=self.user.name,
username=self.user.username,
ip=self.ip,
time=self.time,
time=now,
city=self.city,
)
message = render_to_string('authentication/_msg_different_city.html', context)
return {
'subject': self.subject,
'subject': subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
from users.models import User
user = User.objects.first()
ip = '8.8.8.8'
city = '洛杉矶'
return cls(user, ip, city)

View File

@ -0,0 +1,18 @@
{% load i18n %}
<p>
{% trans 'Hello' %} {{ name }},
</p>
<p>
{% trans 'Your account has remote login behavior, please pay attention' %}
</p>
<p>
<b>{% trans 'Username' %}:</b> {{ username }}<br>
<b>{% trans 'Login time' %}:</b> {{ time }}<br>
<b>{% trans 'Login city' %}:</b> {{ city }}({{ ip }})
</p>
<p>
<small>
{% trans 'If you suspect that the login behavior is abnormal, please modify the account password in time.' %}
</small>
</p>

View File

@ -0,0 +1,15 @@
{% load i18n %}
{% trans 'Hello' %} {{ user.name }},
<br>
{% trans 'Please click the link below to reset your password, if not your request, concern your account security' %}
<br>
<br>
<a href="{{ rest_password_url }}?token={{ rest_password_token}}" class='showLink'>{% trans 'Click here reset password' %}</a>
<br>
<br>
{% trans 'This link is valid for 1 hour. After it expires,' %} <a href="{{ forget_password_url }}?email={{ user.email }}">{% trans 'request new one' %}</a>
<br>
---
<br>
<a href="{{ login_url }}">{% trans 'Login direct' %}</a>
<br>

View File

@ -0,0 +1,18 @@
{% load i18n %}
<p>{% trans 'Hello' %}: {{ name }},</p>
<p>
{% trans 'Your password has just been successfully updated.' %}
</p>
<p>
{% trans 'IP' %}: {{ ip_address }} <br />
{% trans 'Browser' %}: {{ browser }}
</p>
<p>---</p>
<p>
<small>
{% trans 'If the password update was not initiated by you, your account may have security issues.' %} <br />
{% trans 'If you have any questions, you can contact the administrator.' %}
</small>
</p>

View File

@ -50,7 +50,7 @@ class DingTalkQRMixin(PermissionsMixin, View):
def get_verify_state_failed_response(self, redirect_uri):
msg = _("The system configuration is incorrect. Please contact your administrator")
return self.get_failed_reponse(redirect_uri, msg, msg)
return self.get_failed_response(redirect_uri, msg, msg)
def get_qr_url(self, redirect_uri):
state = random_string(16)

View File

@ -303,7 +303,7 @@ class UserLogoutView(TemplateView):
def get_context_data(self, **kwargs):
context = {
'title': _('Logout success'),
'messages': _('Logout success, return login page'),
'message': _('Logout success, return login page'),
'interval': 3,
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,

View File

@ -1,4 +1,4 @@
from common.utils.timezone import now
from common.utils.timezone import local_now
def contains_time_period(time_periods):
@ -8,8 +8,8 @@ def contains_time_period(time_periods):
if not time_periods:
return False
current_time = now().strftime('%H:%M')
today_time_period = next(filter(lambda x: str(x['id']) == now().strftime("%w"), time_periods))
current_time = local_now().strftime('%H:%M')
today_time_period = next(filter(lambda x: str(x['id']) == local_now().strftime("%w"), time_periods))
for time in today_time_period['value'].split(''):
start, end = time.split('~')
end = "24:00" if end == "00:00" else end

View File

@ -20,14 +20,14 @@ def as_current_tz(dt: datetime.datetime):
return astimezone(dt, dj_timezone.get_current_timezone())
def utcnow():
def utc_now():
return dj_timezone.now()
def now():
return as_current_tz(utcnow())
def local_now():
return as_current_tz(utc_now())
_rest_dt_field = DateTimeField()
dt_parser = _rest_dt_field.to_internal_value
dt_formater = _rest_dt_field.to_representation
dt_formatter = _rest_dt_field.to_representation

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:39e9d8c61c6986a067d9c0c82d2a459e07bcc78d07d9bdd1c5934154ea3a198d
size 91321

File diff suppressed because it is too large Load Diff

View File

@ -6,4 +6,5 @@ class NotificationsConfig(AppConfig):
def ready(self):
from . import signals_handler
from . import notifications
super().ready()

View File

@ -1,12 +1,12 @@
from typing import Iterable
import traceback
from html2text import HTML2Text
from typing import Iterable
from itertools import chain
import time
from celery import shared_task
from django.utils.translation import gettext_lazy as _
from common.utils.timezone import now
from common.utils.timezone import local_now
from common.utils import lazyproperty
from users.models import User
from notifications.backends import BACKEND
@ -17,6 +17,7 @@ __all__ = ('SystemMessage', 'UserMessage', 'system_msgs')
system_msgs = []
user_msgs = []
all_msgs = []
class MessageType(type):
@ -58,6 +59,7 @@ class Message(metaclass=MessageType):
message_type_label: str
category: str
category_label: str
text_msg_ignore_links = True
@classmethod
def get_message_type(cls):
@ -66,6 +68,10 @@ class Message(metaclass=MessageType):
def publish_async(self):
return publish_task.delay(self)
@classmethod
def gen_test_msg(cls):
raise NotImplementedError
def publish(self):
raise NotImplementedError
@ -80,31 +86,46 @@ class Message(metaclass=MessageType):
continue
get_msg_method = getattr(self, f'get_{backend}_msg', self.get_common_msg)
try:
msg = get_msg_method()
except NotImplementedError:
continue
client = backend.client()
client.send_msg(users, **msg)
except:
except Exception:
traceback.print_exc()
def send_test_msg(self):
@classmethod
def send_test_msg(cls, ding=True):
msg = cls.gen_test_msg()
if not msg:
return
from users.models import User
users = User.objects.filter(username='admin')
self.send_msg(users, [])
backends = []
if ding:
backends.append(BACKEND.DINGTALK)
msg.send_msg(users, backends)
def get_common_msg(self) -> dict:
raise NotImplementedError
def get_text_msg(self) -> dict:
return self.common_msg
@staticmethod
def get_common_msg() -> dict:
return {
'subject': '',
'message': ''
}
def get_html_msg(self) -> dict:
return self.common_msg
return self.get_common_msg()
def get_text_msg(self) -> dict:
h = HTML2Text()
msg = self.get_html_msg()
content = msg['message']
h.ignore_links = self.text_msg_ignore_links
msg['message'] = h.handle(content)
return msg
@lazyproperty
def common_msg(self) -> dict:
@ -123,7 +144,8 @@ class Message(metaclass=MessageType):
def get_dingtalk_msg(self) -> dict:
# 钉钉相同的消息一天只能发一次,所以给所有消息添加基于时间的序号,使他们不相同
message = self.text_msg['message']
suffix = _('\nTime: {}').format(now())
time = local_now().strftime('%Y-%m-%d %H:%M:%S')
suffix = '\n{}: {}'.format(_('Time'), time)
return {
'subject': self.text_msg['subject'],
@ -144,7 +166,25 @@ class Message(metaclass=MessageType):
def get_sms_msg(self) -> dict:
return self.text_msg
# --------------------------------------------------------------
@classmethod
def test_all_messages(cls):
def get_subclasses(cls):
"""returns all subclasses of argument, cls"""
if issubclass(cls, type):
subclasses = cls.__subclasses__(cls)
else:
subclasses = cls.__subclasses__()
for subclass in subclasses:
subclasses.extend(get_subclasses(subclass))
return subclasses
messages_cls = get_subclasses(cls)
for _cls in messages_cls:
try:
msg = _cls.send_test_msg()
except NotImplementedError:
continue
class SystemMessage(Message):
@ -161,13 +201,16 @@ class SystemMessage(Message):
*subscription.users.all(),
*chain(*[g.users.all() for g in subscription.groups.all()])
]
self.send_msg(users, receive_backends)
@classmethod
def post_insert_to_db(cls, subscription: SystemMsgSubscription):
pass
@classmethod
def gen_test_msg(cls):
raise NotImplementedError
class UserMessage(Message):
user: User
@ -179,7 +222,9 @@ class UserMessage(Message):
"""
发送消息到每个用户配置的接收方式上
"""
sub = UserMsgSubscription.objects.get(user=self.user)
self.send_msg([self.user], sub.receive_backends)
@classmethod
def gen_test_msg(cls):
raise NotImplementedError

View File

@ -1,7 +1,7 @@
from django.db.models import F
from django.db import transaction
from common.utils.timezone import now
from common.utils.timezone import local_now
from common.utils import get_logger
from users.models import User
from .models import SiteMessage as SiteMessageModel, SiteMessageUsers
@ -88,7 +88,7 @@ class SiteMessageUtil:
for site_msg_user in site_msg_users:
site_msg_user.has_read = True
site_msg_user.read_at = now()
site_msg_user.read_at = local_now()
SiteMessageUsers.objects.bulk_update(
site_msg_users, fields=('has_read', 'read_at'))

View File

@ -11,7 +11,7 @@ from django_celery_beat.models import (
PeriodicTask, IntervalSchedule, CrontabSchedule, PeriodicTasks
)
from common.utils.timezone import now
from common.utils.timezone import local_now
from common.utils import get_logger
logger = get_logger(__name__)
@ -52,7 +52,7 @@ def create_or_update_celery_periodic_tasks(tasks):
interval = IntervalSchedule.objects.filter(**kwargs).first()
if interval is None:
interval = IntervalSchedule.objects.create(**kwargs)
last_run_at = now()
last_run_at = local_now()
elif isinstance(detail.get("crontab"), str):
try:
minute, hour, day, month, week = detail["crontab"].split()

View File

@ -24,13 +24,6 @@ class ServerPerformanceMessage(SystemMessage):
'message': self._msg
}
def get_text_msg(self) -> dict:
subject = self._msg[:80]
return {
'subject': subject.replace('<br>', '; '),
'message': self._msg.replace('<br>', '\n')
}
@classmethod
def post_insert_to_db(cls, subscription: SystemMsgSubscription):
admins = User.objects.filter(role=User.ROLE.ADMIN)
@ -38,6 +31,20 @@ class ServerPerformanceMessage(SystemMessage):
subscription.receive_backends = [BACKEND.EMAIL]
subscription.save()
@classmethod
def gen_test_msg(cls):
alarm_messages = []
items_mapper = ServerPerformanceCheckUtil.items_mapper
for item, data in items_mapper.items():
msg = data['alarm_msg_format']
max_threshold = data['max_threshold']
value = 123
msg = msg.format(max_threshold=max_threshold, value=value, name='Fake terminal')
alarm_messages.append(msg)
msg = '<br>'.join(alarm_messages)
return cls(msg)
class ServerPerformanceCheckUtil(object):
items_mapper = {
@ -50,21 +57,21 @@ class ServerPerformanceCheckUtil(object):
'default': 0,
'max_threshold': 80,
'alarm_msg_format': _(
'[Disk] Disk used more than {max_threshold}%: => {value} ({name})'
'Disk used more than {max_threshold}%: => {value} ({name})'
)
},
'memory_used': {
'default': 0,
'max_threshold': 85,
'alarm_msg_format': _(
'[Memory] Memory used more than {max_threshold}%: => {value} ({name})'
'Memory used more than {max_threshold}%: => {value} ({name})'
),
},
'cpu_load': {
'default': 0,
'max_threshold': 5,
'alarm_msg_format': _(
'[CPU] CPU load more than {max_threshold}: => {value} ({name})'
'CPU load more than {max_threshold}: => {value} ({name})'
),
},
}

View File

@ -9,3 +9,4 @@ class PermsConfig(AppConfig):
def ready(self):
super().ready()
from . import signals_handler
from . import notifications

View File

@ -1,310 +1,155 @@
from datetime import datetime
from urllib.parse import urljoin
from django.utils.translation import ugettext as _
from django.conf import settings
from django.template.loader import render_to_string
from common.utils import reverse, get_request_ip_or_data, get_request_user_agent, lazyproperty
from notifications.notifications import UserMessage, SystemMessage
from common.utils import reverse as js_reverse
from notifications.notifications import UserMessage
class AssetPermWillExpireMsg(UserMessage):
class BasePermMsg(UserMessage):
@classmethod
def gen_test_msg(cls):
return
class PermedWillExpireUserMsg(BasePermMsg):
def __init__(self, user, assets):
super().__init__(user)
self.assets = assets
def get_html_msg(self) -> dict:
user = self.user
subject = _('Assets may expire')
assets_text = ','.join(str(asset) for asset in self.assets)
message = _("""
Hello %(name)s:
<br>
Your permissions for the following assets may expire in three days:
<br>
%(assets)s
<br>
Please contact the administrator
""") % {
'name': user.name,
'assets': assets_text
subject = _("You permed assets is about to expire")
context = {
'name': self.user.name,
'items': [str(asset) for asset in self.assets],
'item_type': _("permed assets"),
'show_help': True
}
message = render_to_string('perms/_msg_permed_items_expire.html', context)
return {
'subject': subject,
'message': message
}
def get_text_msg(self) -> dict:
user = self.user
subject = _('Assets may expire')
assets_text = ','.join(str(asset) for asset in self.assets)
message = _("""
Hello %(name)s:
\n
Your permissions for the following assets may expire in three days:
\n
%(assets)s
\n
Please contact the administrator
""") % {
'name': user.name,
'assets': assets_text
}
return {
'subject': subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
from users.models import User
from assets.models import Asset
user = User.objects.first()
assets = Asset.objects.all()[:10]
return cls(user, assets)
class AssetPermWillExpireForOrgAdminMsg(UserMessage):
class AssetPermsWillExpireForOrgAdminMsg(BasePermMsg):
def __init__(self, user, perms, org):
super().__init__(user)
self.perms = perms
self.org = org
def get_html_msg(self) -> dict:
user = self.user
subject = _('Asset permission will expired')
perms_text = ','.join(str(perm) for perm in self.perms)
def get_items_with_url(self):
items_with_url = []
for perm in self.perms:
url = js_reverse(
'perms:asset-permission-detail',
kwargs={'pk': perm.id}, external=True,
api_to_ui=True
) + f'?oid={perm.org_id}'
items_with_url.append([perm.name, url])
return items_with_url
message = _("""
Hello %(name)s:
<br>
The following asset permissions of organization %(org) will expire in three days
<br>
%(perms)s
""") % {
'name': user.name,
'org': self.org,
'perms': perms_text
def get_html_msg(self):
items_with_url = self.get_items_with_url()
subject = _("Asset permissions is about to expire")
context = {
'name': self.user.name,
'items_with_url': items_with_url,
'item_type': _('asset permissions of organization {}').format(self.org)
}
message = render_to_string('perms/_msg_item_permissions_expire.html', context)
return {
'subject': subject,
'message': message
}
def get_text_msg(self) -> dict:
user = self.user
subject = _('Asset permission will expired')
perms_text = ','.join(str(perm) for perm in self.perms)
@classmethod
def gen_test_msg(cls):
from users.models import User
from perms.models import AssetPermission
from orgs.models import Organization
message = _("""
Hello %(name)s:
\n
The following asset permissions of organization %(org) will expire in three days
\n
%(perms)s
""") % {
'name': user.name,
'org': self.org,
'perms': perms_text
}
return {
'subject': subject,
'message': message
}
user = User.objects.first()
perms = AssetPermission.objects.all()[:10]
org = Organization.objects.first()
return cls(user, perms, org)
class AssetPermWillExpireForAdminMsg(UserMessage):
def __init__(self, user, org_perm_mapper: dict):
super().__init__(user)
self.org_perm_mapper = org_perm_mapper
def get_html_msg(self) -> dict:
user = self.user
subject = _('Asset permission will expired')
content = ''
for org, perms in self.org_perm_mapper.items():
content += f'<br> Orgnization: {org} <br> Permissions: {",".join(str(perm) for perm in perms)} <br>'
message = _("""
Hello %(name)s:
<br>
The following asset permissions will expire in three days
<br>
%(content)s
""") % {
'name': user.name,
'content': content,
}
return {
'subject': subject,
'message': message
}
def get_text_msg(self) -> dict:
user = self.user
subject = _('Asset permission will expired')
content = ''
for org, perms in self.org_perm_mapper.items():
content += f'\n Orgnization: {org} \n Permissions: {perms} \n'
message = _("""
Hello %(name)s:
\n
The following asset permissions of organization %(org) will expire in three days
\n
%(content)s
""") % {
'name': user.name,
'content': content,
}
return {
'subject': subject,
'message': message
}
class AppPermWillExpireMsg(UserMessage):
class PermedAppsWillExpireUserMsg(BasePermMsg):
def __init__(self, user, apps):
super().__init__(user)
self.apps = apps
def get_html_msg(self) -> dict:
user = self.user
subject = _('Applications may expire')
apps_text = ','.join(str(app) for app in self.apps)
message = _("""
Hello %(name)s:
<br>
Your permissions for the following applications may expire in three days:
<br>
%(apps)s
<br>
Please contact the administrator
""") % {
'name': user.name,
'apps': apps_text
subject = _("Your permed applications is about to expire")
context = {
'name': self.user.name,
'item_type': _('permed applications'),
'items': [str(app) for app in self.apps]
}
message = render_to_string('perms/_msg_permed_items_expire.html', context)
return {
'subject': subject,
'message': message
}
def get_text_msg(self) -> dict:
user = self.user
subject = _('Applications may expire')
apps_text = ','.join(str(app) for app in self.apps)
@classmethod
def gen_test_msg(cls):
from users.models import User
from applications.models import Application
message = _("""
Hello %(name)s:
\n
Your permissions for the following applications may expire in three days:
\n
%(apps)s
\n
Please contact the administrator
""") % {
'name': user.name,
'apps': apps_text
}
return {
'subject': subject,
'message': message
}
user = User.objects.first()
apps = Application.objects.all()[:10]
return cls(user, apps)
class AppPermWillExpireForOrgAdminMsg(UserMessage):
class AppPermsWillExpireForOrgAdminMsg(BasePermMsg):
def __init__(self, user, perms, org):
super().__init__(user)
self.perms = perms
self.org = org
def get_html_msg(self) -> dict:
user = self.user
subject = _('Application permission will expired')
perms_text = ','.join(str(perm) for perm in self.perms)
message = _("""
Hello %(name)s:
<br>
The following application permissions of organization %(org) will expire in three days
<br>
%(perms)s
""") % {
'name': user.name,
'org': self.org,
'perms': perms_text
}
return {
'subject': subject,
'message': message
}
def get_text_msg(self) -> dict:
user = self.user
subject = _('Application permission will expired')
perms_text = ','.join(str(perm) for perm in self.perms)
message = _("""
Hello %(name)s:
\n
The following application permissions of organization %(org) will expire in three days
\n
%(perms)s
""") % {
'name': user.name,
'org': self.org,
'perms': perms_text
}
return {
'subject': subject,
'message': message
}
class AppPermWillExpireForAdminMsg(UserMessage):
def __init__(self, user, org_perm_mapper: dict):
super().__init__(user)
self.org_perm_mapper = org_perm_mapper
def get_items_with_url(self):
items_with_url = []
for perm in self.perms:
url = js_reverse(
'perms:application-permission-detail',
kwargs={'pk': perm.id}, external=True,
api_to_ui=True
) + f'?oid={perm.org_id}'
items_with_url.append([perm.name, url])
return items_with_url
def get_html_msg(self) -> dict:
user = self.user
subject = _('Application permission will expired')
content = ''
for org, perms in self.org_perm_mapper.items():
content += f'<br>Orgnization: {org} <br> Permissions: {",".join(str(perm) for perm in perms)} <br>'
message = _("""
Hello %(name)s:
<br>
The following application permissions will expire in three days
<br>
%(content)s
""") % {
'name': user.name,
'content': content,
items = self.get_items_with_url()
subject = _('Application permissions is about to expire')
context = {
'name': self.user.name,
'item_type': _('application permissions of organization {}').format(self.org),
'items_with_url': items
}
message = render_to_string('perms/_msg_item_permissions_expire.html', context)
return {
'subject': subject,
'message': message
}
def get_text_msg(self) -> dict:
user = self.user
subject = _('Application permission will expired')
@classmethod
def gen_test_msg(cls):
from users.models import User
from perms.models import ApplicationPermission
from orgs.models import Organization
content = ''
for org, perms in self.org_perm_mapper.items():
content += f'\n Orgnization: {org} \n Permissions: {perms} \n'
message = _("""
Hello %(name)s:
\n
The following application permissions of organization %(org) will expire in three days
\n
%(content)s
""") % {
'name': user.name,
'content': content,
}
return {
'subject': subject,
'message': message
}
user = User.objects.first()
perms = ApplicationPermission.objects.all()[:10]
org = Organization.objects.first()
return cls(user, perms, org)

View File

@ -7,14 +7,13 @@ from django.db.transaction import atomic
from django.conf import settings
from celery import shared_task
from users.models import User
from orgs.utils import tmp_to_root_org
from common.utils import get_logger
from common.utils.timezone import now, dt_formater, dt_parser
from common.utils.timezone import local_now, dt_formatter, dt_parser
from ops.celery.decorator import register_as_period_task
from perms.notifications import (
AssetPermWillExpireMsg, AssetPermWillExpireForOrgAdminMsg, AssetPermWillExpireForAdminMsg,
AppPermWillExpireMsg, AppPermWillExpireForOrgAdminMsg, AppPermWillExpireForAdminMsg,
PermedWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg,
PermedAppsWillExpireUserMsg, AppPermsWillExpireForOrgAdminMsg
)
from perms.models import AssetPermission, ApplicationPermission
from perms.utils.asset.user_permission import UserGrantedTreeRefreshController
@ -34,10 +33,10 @@ def check_asset_permission_expired():
setting_name = 'last_asset_perm_expired_check'
end = now()
end = local_now()
default_start = end - timedelta(days=36000) # Long long ago in china
defaults = {'value': dt_formater(default_start)}
defaults = {'value': dt_formatter(default_start)}
setting, created = Setting.objects.get_or_create(
name=setting_name, defaults=defaults
)
@ -45,7 +44,7 @@ def check_asset_permission_expired():
start = default_start
else:
start = dt_parser(setting.value)
setting.value = dt_formater(end)
setting.value = dt_formatter(end)
setting.save()
asset_perm_ids = AssetPermission.objects.filter(
@ -61,14 +60,15 @@ def check_asset_permission_expired():
@atomic()
@tmp_to_root_org()
def check_asset_permission_will_expired():
start = now()
start = local_now()
end = start + timedelta(days=3)
user_asset_mapper = defaultdict(set)
org_perm_mapper = defaultdict(set)
asset_perms = AssetPermission.objects.filter(
date_expired__gte=start, date_expired__lte=end
date_expired__gte=start,
date_expired__lte=end
).distinct()
for asset_perm in asset_perms:
@ -83,18 +83,12 @@ def check_asset_permission_will_expired():
user_asset_mapper[u].update(assets)
for user, assets in user_asset_mapper.items():
AssetPermWillExpireMsg(user, assets).publish_async()
admins = User.objects.filter(role=User.ROLE.ADMIN)
if org_perm_mapper:
for admin in admins:
AssetPermWillExpireForAdminMsg(admin, org_perm_mapper).publish_async()
PermedWillExpireUserMsg(user, assets).publish_async()
for org, perms in org_perm_mapper.items():
org_admins = org.admins.exclude(role=User.ROLE.ADMIN)
org_admins = org.admins.all()
for org_admin in org_admins:
AssetPermWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async()
AssetPermsWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async()
@register_as_period_task(crontab='0 10 * * *')
@ -102,11 +96,12 @@ def check_asset_permission_will_expired():
@atomic()
@tmp_to_root_org()
def check_app_permission_will_expired():
start = now()
start = local_now()
end = start + timedelta(days=3)
app_perms = ApplicationPermission.objects.filter(
date_expired__gte=start, date_expired__lte=end
date_expired__gte=start,
date_expired__lte=end
).distinct()
user_app_mapper = defaultdict(set)
@ -121,15 +116,9 @@ def check_app_permission_will_expired():
user_app_mapper[u].update(apps)
for user, apps in user_app_mapper.items():
AppPermWillExpireMsg(user, apps).publish_async()
admins = User.objects.filter(role=User.ROLE.ADMIN)
if org_perm_mapper:
for admin in admins:
AppPermWillExpireForAdminMsg(admin, org_perm_mapper).publish_async()
PermedAppsWillExpireUserMsg(user, apps).publish_async()
for org, perms in org_perm_mapper.items():
org_admins = org.admins.exclude(role=User.ROLE.ADMIN)
org_admins = org.admins.all()
for org_admin in org_admins:
AppPermWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async()
AppPermsWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async()

View File

@ -0,0 +1,16 @@
{% load i18n %}
<p>
{% trans 'Hello' %} {{ name }},
</p>
<p>
{% blocktranslate %}
The following {{ item_type }} will expire in 3 days
{% endblocktranslate %}
</p>
<ul>
{% for item, url in items_with_url %}
<li><a href="{{ url }}">{{ item }}</a></li>
{% endfor %}
</ul>

View File

@ -0,0 +1,24 @@
{% load i18n %}
<p>
{% trans 'Hello' %} {{ name }},
</p>
<p>
{% blocktranslate %}
The following {{ item_type }} will expire in 3 days
{% endblocktranslate %}
</p>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
<br />
<p>
---<br />
<small>
{% trans 'If you have any question, please contact the administrator' %}
</small>
</p>

View File

@ -111,8 +111,8 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri
help_text=_("Allow terminal register, after all terminal setup, you should disable this for security")
)
SECURITY_WATERMARK_ENABLED = serializers.BooleanField(
required=True, label=_('Replay watermark'),
help_text=_('Enabled, the session replay contains watermark information')
required=True, label=_('Enable watermark'),
help_text=_('Enabled, the web session and replay contains watermark information')
)
SECURITY_MAX_IDLE_TIME = serializers.IntegerField(
min_value=1, max_value=99999, required=False,

View File

@ -1,7 +1,9 @@
from typing import Callable
import textwrap
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.template.loader import render_to_string
from users.models import User
from common.utils import get_logger, reverse
@ -68,60 +70,21 @@ class CommandAlertMessage(CommandAlertMixin, SystemMessage):
def __init__(self, command):
self.command = command
def get_text_msg(self) -> dict:
command = self.command
with tmp_to_root_org():
session = Session.objects.get(id=command['session'])
session_detail_url = reverse(
'api-terminal:session-detail', kwargs={'pk': command['session']},
external=True, api_to_ui=True
)
message = _("""
Command: %(command)s
Asset: %(hostname)s (%(host_ip)s)
User: %(user)s
Level: %(risk_level)s
Session: %(session_detail_url)s?oid=%(oid)s
""") % {
'command': command['input'],
'hostname': command['asset'],
'host_ip': session.asset_obj.ip,
'user': command['user'],
'risk_level': Command.get_risk_level_str(command['risk_level']),
'session_detail_url': session_detail_url,
'oid': session.org_id
}
return {
'subject': self.subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
command = Command.objects.first().to_dict()
return cls(command)
def get_html_msg(self) -> dict:
command = self.command
with tmp_to_root_org():
session = Session.objects.get(id=command['session'])
session_detail_url = reverse(
'api-terminal:session-detail', kwargs={'pk': command['session']},
external=True, api_to_ui=True
)
message = _("""
Command: %(command)s
<br>
Asset: %(hostname)s (%(host_ip)s)
<br>
User: %(user)s
<br>
Level: %(risk_level)s
<br>
Session: <a href="%(session_detail_url)s?oid=%(oid)s">session detail</a>
<br>
""") % {
context = {
'command': command['input'],
'hostname': command['asset'],
'host_ip': session.asset_obj.ip,
@ -130,6 +93,7 @@ Session: %(session_detail_url)s?oid=%(oid)s
'session_detail_url': session_detail_url,
'oid': session.org_id
}
message = render_to_string('terminal/_msg_command_alert.html', context)
return {
'subject': self.subject,
'message': message
@ -144,53 +108,35 @@ class CommandExecutionAlert(CommandAlertMixin, SystemMessage):
def __init__(self, command):
self.command = command
@classmethod
def gen_test_msg(cls):
from assets.models import Asset
from users.models import User
cmd = {
'input': 'ifconfig eth0',
'assets': Asset.objects.all()[:10],
'user': str(User.objects.first()),
'risk_level': 5,
}
return cls(cmd)
def get_html_msg(self) -> dict:
command = self.command
_input = command['input']
_input = _input.replace('\n', '<br>')
assets = ', '.join([str(asset) for asset in command['assets']])
message = _("""
Assets: %(assets)s
<br>
User: %(user)s
<br>
Level: %(risk_level)s
<br>
assets_with_url = []
for asset in command['assets']:
url = reverse('assets:asset-detail', kwargs={'pk': asset.id}, api_to_ui=True, external=True)
assets_with_url.append([asset, url])
----------------- Commands ---------------- <br>
%(command)s <br>
----------------- Commands ---------------- <br>
""") % {
context = {
'command': _input,
'assets': assets,
'user': command['user'],
'risk_level': Command.get_risk_level_str(command['risk_level'])
}
return {
'subject': self.subject,
'message': message
}
def get_text_msg(self) -> dict:
command = self.command
_input = command['input']
assets = ', '.join([str(asset) for asset in command['assets']])
message = _("""
Assets: %(assets)s
User: %(user)s
Level: %(risk_level)s
Commands 👇 ------------
%(command)s
------------------------
""") % {
'command': _input,
'assets': assets,
'assets_with_url': assets_with_url,
'user': command['user'],
'risk_level': Command.get_risk_level_str(command['risk_level'])
}
message = render_to_string('terminal/_msg_command_execute_alert.html', context)
return {
'subject': self.subject,
'message': message

View File

@ -0,0 +1,17 @@
{% load i18n %}
<p>
<b>{% trans 'Command' %}:</b> {{ command }}
</p>
<p>
<b>{% trans 'Asset' %}:</b> {{ hostname }}({{ host_ip }})
</p>
<p>
<b>{% trans 'User' %}:</b> {{ user }}
</p>
<p>
<b>{% trans 'Level' %}:</b> {{ risk_level }}
</p>
<p>
<b>{% trans 'Session' %}:</b> <a href="{{ session_detail_url}}?oid={{ oid }}">{% trans 'view' %}</a>
</p>

View File

@ -0,0 +1,24 @@
{% load i18n %}
<p>
<b>{% trans 'User' %}:</b> {{ user }}
</p>
<p>
<b>{% trans 'Level' %}:</b> {{ risk_level }}
</p>
<div>
<b>{% trans 'Command' %}: </b><br>
<pre>
{{ command }}
</pre>
</div>
<div>
<b>{% trans 'Assets' %}:</b><br>
<ul>
{% for asset, url in assets_with_url %}
<li>
<a href="{{ url }}">{{ asset }}</a>
</li>
{% endfor %}
</ul>
</div>

View File

@ -6,4 +6,5 @@ class TicketsConfig(AppConfig):
def ready(self):
from . import signals_handler
from . import notifications
return super().ready()

View File

@ -1,70 +1,49 @@
from urllib.parse import urljoin
from django.conf import settings
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from . import const
from notifications.notifications import UserMessage
from common.utils import get_logger
from .models import Ticket
logger = get_logger(__file__)
EMAIL_TEMPLATE = '''
<div>
<p>
{title}
</p>
<div>
{body}
</div>
<div>
<a href={ticket_detail_url}>
<strong>{ticket_detail_url_description}</strong>
</a>
</div>
</div>
'''
class BaseTicketMessage(UserMessage):
title: ''
ticket: Ticket
content_title: str
@property
def subject(self):
return _(self.title).format(self.ticket.title, self.ticket.get_type_display())
return self.title.format(self.ticket.title, self.ticket.get_type_display())
@property
def ticket_detail_url(self):
return urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(self.ticket.id)))
def get_text_msg(self) -> dict:
message = """
{title}: {ticket_detail_url}
{body}
""".format(
def get_html_msg(self) -> dict:
context = dict(
title=self.content_title,
ticket_detail_url=self.ticket_detail_url,
body=self.ticket.body.replace('<div style="margin-left: 20px;">', '').replace('</div>', '')
body=self.ticket.body.replace('\n', '<br/>'),
)
message = render_to_string('tickets/_msg_ticket.html', context)
return {
'subject': self.subject,
'message': message
}
def get_html_msg(self) -> dict:
message = EMAIL_TEMPLATE.format(
title=self.content_title,
ticket_detail_url=self.ticket_detail_url,
ticket_detail_url_description=_('click here to review'),
body=self.ticket.body.replace('\n', '<br/>'),
)
return {
'subject': self.subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
return cls(None)
class TicketAppliedToAssignee(BaseTicketMessage):
title = 'New Ticket - {} ({})'
title = _('New Ticket - {} ({})')
def __init__(self, user, ticket):
self.ticket = ticket
@ -72,11 +51,21 @@ class TicketAppliedToAssignee(BaseTicketMessage):
@property
def content_title(self):
return _('Your has a new ticket, applicant - {}').format(str(self.ticket.applicant_display))
return _('Your has a new ticket, applicant - {}').format(
str(self.ticket.applicant_display)
)
@classmethod
def gen_test_msg(cls):
from .models import Ticket
from users.models import User
ticket = Ticket.objects.first()
user = User.objects.first()
return cls(user, ticket)
class TicketProcessedToApplicant(BaseTicketMessage):
title = 'Ticket has processed - {} ({})'
title = _('Ticket has processed - {} ({})')
def __init__(self, user, ticket, processor):
self.ticket = ticket
@ -86,3 +75,12 @@ class TicketProcessedToApplicant(BaseTicketMessage):
@property
def content_title(self):
return _('Your ticket has been processed, processor - {}').format(str(self.processor))
@classmethod
def gen_test_msg(cls):
from .models import Ticket
from users.models import User
ticket = Ticket.objects.first()
user = User.objects.first()
processor = User.objects.last()
return cls(user, ticket, processor)

View File

@ -0,0 +1,14 @@
{% load i18n %}
<div>
<p>
{{ title}}
</p>
<div>
{{ body}}
</div>
<div>
<a href={{ ticket_detail_url }}>
{% trans 'Click here to review' %}
</a>
</div>
</div>

View File

@ -8,4 +8,5 @@ class UsersConfig(AppConfig):
def ready(self):
from . import signals_handler
from . import notifications
super().ready()

View File

@ -1,84 +1,42 @@
from datetime import datetime
from urllib.parse import urljoin
from django.utils import timezone
from django.utils.translation import ugettext as _
from django.conf import settings
from django.template.loader import render_to_string
from common.utils import reverse, get_request_ip_or_data, get_request_user_agent, lazyproperty
from common.utils import reverse, get_request_ip_or_data, get_request_user_agent
from notifications.notifications import UserMessage
class ResetPasswordMsg(UserMessage):
def __init__(self, user):
super().__init__(user)
self.reset_passwd_token = user.generate_reset_token()
def get_text_msg(self) -> dict:
user = self.user
subject = _('Reset password')
message = _("""
Hello %(name)s:
Please click the link below to reset your password, if not your request, concern your account security
Click here reset password 👇
%(rest_password_url)s?token=%(rest_password_token)s
This link is valid for 1 hour. After it expires,
request new one 👇
%(forget_password_url)s?email=%(email)s
-------------------
Login direct 👇
%(login_url)s
""") % {
'name': user.name,
'rest_password_url': reverse('authentication:reset-password', external=True),
'rest_password_token': self.reset_passwd_token,
'forget_password_url': reverse('authentication:forgot-password', external=True),
'email': user.email,
'login_url': reverse('authentication:login', external=True),
}
return {
'subject': subject,
'message': message
}
def get_html_msg(self) -> dict:
user = self.user
subject = _('Reset password')
message = _("""
Hello %(name)s:
<br>
Please click the link below to reset your password, if not your request, concern your account security
<br>
<a href="%(rest_password_url)s?token=%(rest_password_token)s">Click here reset password</a>
<br>
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>
<br>
---
<br>
<a href="%(login_url)s">Login direct</a>
<br>
""") % {
'name': user.name,
context = {
'user': user,
'rest_password_url': reverse('authentication:reset-password', external=True),
'rest_password_token': self.reset_passwd_token,
'forget_password_url': reverse('authentication:forgot-password', external=True),
'email': user.email,
'login_url': reverse('authentication:login', external=True),
}
message = render_to_string('authentication/_msg_reset_password.html', context)
return {
'subject': subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
from users.models import User
user = User.objects.first()
return cls(user)
class ResetPasswordSuccessMsg(UserMessage):
def __init__(self, user, request):
@ -86,280 +44,119 @@ class ResetPasswordSuccessMsg(UserMessage):
self.ip_address = get_request_ip_or_data(request)
self.browser = get_request_user_agent(request)
def get_text_msg(self) -> dict:
user = self.user
subject = _('Reset password success')
message = _("""
Hi %(name)s:
Your JumpServer password has just been successfully updated.
If the password update was not initiated by you, your account may have security issues.
It is recommended that you log on to the JumpServer immediately and change your password.
If you have any questions, you can contact the administrator.
-------------------
IP Address: %(ip_address)s
\n
Browser: %(browser)s
""") % {
'name': user.name,
'ip_address': self.ip_address,
'browser': self.browser,
}
return {
'subject': subject,
'message': message
}
def get_html_msg(self) -> dict:
user = self.user
subject = _('Reset password success')
message = _("""
Hi %(name)s:
<br>
<br>
Your JumpServer password has just been successfully updated.
<br>
<br>
If the password update was not initiated by you, your account may have security issues.
It is recommended that you log on to the JumpServer immediately and change your password.
<br>
<br>
If you have any questions, you can contact the administrator.
<br>
<br>
---
<br>
<br>
IP Address: %(ip_address)s
<br>
<br>
Browser: %(browser)s
<br>
""") % {
context = {
'name': user.name,
'ip_address': self.ip_address,
'browser': self.browser,
}
message = render_to_string('authentication/_msg_rest_password_success.html', context)
return {
'subject': subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
from users.models import User
from rest_framework.test import APIRequestFactory
from rest_framework.request import Request
factory = APIRequestFactory()
request = Request(factory.get('/notes/'))
user = User.objects.first()
return cls(user, request)
class PasswordExpirationReminderMsg(UserMessage):
update_password_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=PasswordUpdate')
def get_text_msg(self) -> dict:
user = self.user
subject = _('Security notice')
message = _("""
Hello %(name)s:
Your password will expire in %(date_password_expired)s,
For your account security, please click on the link below to update your password in time
Click here update password 👇
%(update_password_url)s
If your password has expired, please click 👇 to apply for a password reset email.
%(forget_password_url)s?email=%(email)s
-------------------
Login direct 👇
%(login_url)s
""") % {
'name': user.name,
'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
user.date_password_expired)).strftime('%Y-%m-%d %H:%M'),
'update_password_url': self.update_password_url,
'forget_password_url': reverse('authentication:forgot-password', external=True),
'email': user.email,
'login_url': reverse('authentication:login', external=True),
}
return {
'subject': subject,
'message': message
}
def get_html_msg(self) -> dict:
user = self.user
subject = _('Password is about expire')
subject = _('Security notice')
message = _("""
Hello %(name)s:
<br>
Your password will expire in %(date_password_expired)s,
<br>
For your account security, please click on the link below to update your password in time
<br>
<a href="%(update_password_url)s">Click here update password</a>
<br>
If your password has expired, please click
<a href="%(forget_password_url)s?email=%(email)s">Password expired</a>
to apply for a password reset email.
<br>
---
<br>
<a href="%(login_url)s">Login direct</a>
<br>
""") % {
date_password_expired_local = timezone.localtime(user.date_password_expired)
date_password_expired = date_password_expired_local.strftime('%Y-%m-%d %H:%M:%S')
context = {
'name': user.name,
'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
user.date_password_expired)).strftime('%Y-%m-%d %H:%M'),
'date_password_expired': date_password_expired,
'update_password_url': self.update_password_url,
'forget_password_url': reverse('authentication:forgot-password', external=True),
'email': user.email,
'login_url': reverse('authentication:login', external=True),
}
message = render_to_string('users/_msg_password_expire_reminder.html', context)
return {
'subject': subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
from users.models import User
user = User.objects.get(username='admin')
return cls(user)
class UserExpirationReminderMsg(UserMessage):
def get_text_msg(self) -> dict:
subject = _('Expiration notice')
message = _("""
Hello %(name)s:
Your account will expire in %(date_expired)s,
In order not to affect your normal work, please contact the administrator for confirmation.
""") % {
'name': self.user.name,
'date_expired': datetime.fromtimestamp(datetime.timestamp(
self.user.date_expired)).strftime('%Y-%m-%d %H:%M'),
}
return {
'subject': subject,
'message': message
}
def get_html_msg(self) -> dict:
subject = _('Expiration notice')
message = _("""
Hello %(name)s:
<br>
Your account will expire in %(date_expired)s,
<br>
In order not to affect your normal work, please contact the administrator for confirmation.
<br>
""") % {
'name': self.user.name,
'date_expired': datetime.fromtimestamp(datetime.timestamp(
self.user.date_expired)).strftime('%Y-%m-%d %H:%M'),
subject = _('Account is about expire')
date_expired_local = timezone.localtime(self.user.date_password_expired)
date_expired = date_expired_local.strftime('%Y-%m-%d %H:%M:%S')
context = {
'name': self.user.name,
'date_expired': date_expired
}
message = render_to_string('users/_msg_account_expire_reminder.html', context)
return {
'subject': subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
from users.models import User
user = User.objects.get(username='admin')
return cls(user)
class ResetSSHKeyMsg(UserMessage):
def get_text_msg(self) -> dict:
subject = _('SSH Key Reset')
message = _("""
Hello %(name)s:
Your ssh public key has been reset by site administrator.
Please login and reset your ssh public key.
Login direct 👇
%(login_url)s
""") % {
'name': self.user.name,
'login_url': reverse('authentication:login', external=True),
}
return {
'subject': subject,
'message': message
}
def get_html_msg(self) -> dict:
subject = _('SSH Key Reset')
message = _("""
Hello %(name)s:
<br>
Your ssh public key has been reset by site administrator.
Please login and reset your ssh public key.
<br>
<a href="%(login_url)s">Login direct</a>
<br>
""") % {
subject = _('Reset SSH Key')
context = {
'name': self.user.name,
'login_url': reverse('authentication:login', external=True),
}
message = render_to_string('users/_msg_reset_ssh_key.html', context)
return {
'subject': subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
from users.models import User
user = User.objects.get(username='admin')
return cls(user)
class ResetMFAMsg(UserMessage):
def get_text_msg(self) -> dict:
subject = _('MFA Reset')
message = _("""
Hello %(name)s:
Your MFA has been reset by site administrator.
Please login and reset your MFA.
Login direct 👇
%(login_url)s
""") % {
'name': self.user.name,
'login_url': reverse('authentication:login', external=True),
}
return {
'subject': subject,
'message': message
}
def get_html_msg(self) -> dict:
subject = _('MFA Reset')
message = _("""
Hello %(name)s:
<br>
Your MFA has been reset by site administrator.
Please login and reset your MFA.
<br>
<a href="%(login_url)s">Login direct</a>
<br>
""") % {
subject = _('Reset MFA')
context = {
'name': self.user.name,
'login_url': reverse('authentication:login', external=True),
}
message = render_to_string('users/_msg_reset_mfa.html', context)
return {
'subject': subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
from users.models import User
user = User.objects.get(username='admin')
return cls(user)

View File

@ -0,0 +1,9 @@
{% load i18n %}
<p>
{% trans 'Hello' %} {{ name }},
</p>
<p>
{% trans 'Your account will expire in' %} {{ date_expired }}, <br />
{% trans 'In order not to affect your normal work, please contact the administrator for confirmation.' %}
</p>

View File

@ -0,0 +1,22 @@
{% load i18n %}
<p>
{% trans 'Hello' %} {{ name }},
</p>
<p>
{% trans 'Your password will expire in' %} {{ date_password_expired }}, <br />
{% trans 'For your account security, please click on the link below to update your password in time' %}
<br />
<br />
<a href="{{ update_password_url }}">{% trans 'Click here update password' %}</a>
<br />
</p>
<p>
{% trans 'If your password has expired, please click' %}
<a href="{{ forget_password_url }}?email={{ email }}">{% trans 'Reset password' %}</a>
{% trans 'to apply for a password reset email.' %}
</p>
---
<br>
<a href="{{ login_url }}">{% trans 'Login direct' %}</a>

View File

@ -0,0 +1,12 @@
{% load i18n %}
<p>
{% trans 'Hello' %} {{ name }},
</p>
<p>
{% trans 'Your MFA has been reset by site administrator.' %} <br />
{% trans 'Please login and reset your MFA.' %}
</p>
<p>
<a href="{{ login_url }}">{% trans 'Login direct' %}</a>
</p>

View File

@ -0,0 +1,12 @@
{% load i18n %}
<p>
{% trans 'Hello' %} {{ name }},
</p>
<p>
{% trans 'Your ssh public key has been reset by site administrator.' %} <br />
{% trans 'Please login and reset your ssh public key.' %}
</p>
<p>
<a href="{{ login_url }}">{% trans 'Login direct' %}</a>
</p>

View File

@ -190,7 +190,7 @@ class UserOtpSettingsSuccessView(TemplateView):
title, describe = self.get_title_describe()
context = {
'title': title,
'messages': describe,
'message': describe,
'interval': 1,
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,

View File

@ -119,3 +119,4 @@ cx-Oracle==8.2.1
psycopg2-binary==2.9.1
alibabacloud_dysmsapi20170525==2.0.2
geoip2==4.4.0
html2text==2020.1.16