mirror of https://github.com/jumpserver/jumpserver
perf: 优化消息通知 (#7024)
* perf: 优化系统用户列表 * stash * perf: 优化消息通知 * perf: 修改钉钉 * perf: 修改优化消息通知 * perf: 修改requirements * perf: 优化datetime Co-authored-by: ibuler <ibuler@qq.com>pull/7033/head
parent
9acfd461b4
commit
00d434ceea
|
@ -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 ''
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -6,5 +6,6 @@ class AuthenticationConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
from . import signals_handlers
|
||||
from . import notifications
|
||||
super().ready()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -6,4 +6,5 @@ class NotificationsConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
from . import notifications
|
||||
super().ready()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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})'
|
||||
),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -9,3 +9,4 @@ class PermsConfig(AppConfig):
|
|||
def ready(self):
|
||||
super().ready()
|
||||
from . import signals_handler
|
||||
from . import notifications
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -6,4 +6,5 @@ class TicketsConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
from . import notifications
|
||||
return super().ready()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
|
@ -8,4 +8,5 @@ class UsersConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
from . import notifications
|
||||
super().ready()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue