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 return allow, reject_type
@staticmethod @staticmethod
def construct_confirm_ticket_meta(request=None): def construct_confirm_ticket_meta(request=None):
login_ip = get_request_ip(request) if request else '' login_ip = get_request_ip(request) if request else ''

View File

@ -187,7 +187,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ 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 return queryset

View File

@ -9,7 +9,7 @@ from rest_framework.response import Response
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.permissions import AllowAny 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.const.http import POST, GET
from common.drf.api import JMSGenericViewSet from common.drf.api import JMSGenericViewSet
from common.drf.serializers import EmptySerializer from common.drf.serializers import EmptySerializer
@ -79,7 +79,7 @@ class SSOViewSet(AuthMixin, JMSGenericViewSet):
return HttpResponseRedirect(next_url) 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') self.send_auth_signal(success=False, reason='authkey_timeout')
return HttpResponseRedirect(next_url) return HttpResponseRedirect(next_url)

View File

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

View File

@ -1,25 +1,12 @@
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
from notifications.notifications import UserMessage from notifications.notifications import UserMessage
from settings.api import PublicSettingApi
from common.utils import get_logger from common.utils import get_logger
logger = get_logger(__file__) 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): class DifferentCityLoginMessage(UserMessage):
def __init__(self, user, ip, city): def __init__(self, user, ip, city):
@ -27,49 +14,28 @@ class DifferentCityLoginMessage(UserMessage):
self.city = city self.city = city
super().__init__(user) 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: def get_html_msg(self) -> dict:
message = EMAIL_TEMPLATE.format( now_local = timezone.localtime(timezone.now())
subject=self.subject, now = now_local.strftime("%Y-%m-%d %H:%M:%S")
server_name=PublicSettingApi.get_login_title(), subject = _('Different city login reminder')
context = dict(
subject=subject,
name=self.user.name,
username=self.user.username, username=self.user.username,
ip=self.ip, ip=self.ip,
time=self.time, time=now,
city=self.city, city=self.city,
) )
message = render_to_string('authentication/_msg_different_city.html', context)
return { return {
'subject': self.subject, 'subject': subject,
'message': message '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): def get_verify_state_failed_response(self, redirect_uri):
msg = _("The system configuration is incorrect. Please contact your administrator") 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): def get_qr_url(self, redirect_uri):
state = random_string(16) state = random_string(16)

View File

@ -303,7 +303,7 @@ class UserLogoutView(TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'title': _('Logout success'), 'title': _('Logout success'),
'messages': _('Logout success, return login page'), 'message': _('Logout success, return login page'),
'interval': 3, 'interval': 3,
'redirect_url': reverse('authentication:login'), 'redirect_url': reverse('authentication:login'),
'auto_redirect': True, '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): def contains_time_period(time_periods):
@ -8,8 +8,8 @@ def contains_time_period(time_periods):
if not time_periods: if not time_periods:
return False return False
current_time = now().strftime('%H:%M') current_time = local_now().strftime('%H:%M')
today_time_period = next(filter(lambda x: str(x['id']) == now().strftime("%w"), time_periods)) today_time_period = next(filter(lambda x: str(x['id']) == local_now().strftime("%w"), time_periods))
for time in today_time_period['value'].split(''): for time in today_time_period['value'].split(''):
start, end = time.split('~') start, end = time.split('~')
end = "24:00" if end == "00:00" else end 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()) return astimezone(dt, dj_timezone.get_current_timezone())
def utcnow(): def utc_now():
return dj_timezone.now() return dj_timezone.now()
def now(): def local_now():
return as_current_tz(utcnow()) return as_current_tz(utc_now())
_rest_dt_field = DateTimeField() _rest_dt_field = DateTimeField()
dt_parser = _rest_dt_field.to_internal_value 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): def ready(self):
from . import signals_handler from . import signals_handler
from . import notifications
super().ready() super().ready()

View File

@ -1,12 +1,12 @@
from typing import Iterable
import traceback import traceback
from html2text import HTML2Text
from typing import Iterable
from itertools import chain from itertools import chain
import time
from celery import shared_task from celery import shared_task
from django.utils.translation import gettext_lazy as _ 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 common.utils import lazyproperty
from users.models import User from users.models import User
from notifications.backends import BACKEND from notifications.backends import BACKEND
@ -17,6 +17,7 @@ __all__ = ('SystemMessage', 'UserMessage', 'system_msgs')
system_msgs = [] system_msgs = []
user_msgs = [] user_msgs = []
all_msgs = []
class MessageType(type): class MessageType(type):
@ -58,6 +59,7 @@ class Message(metaclass=MessageType):
message_type_label: str message_type_label: str
category: str category: str
category_label: str category_label: str
text_msg_ignore_links = True
@classmethod @classmethod
def get_message_type(cls): def get_message_type(cls):
@ -66,6 +68,10 @@ class Message(metaclass=MessageType):
def publish_async(self): def publish_async(self):
return publish_task.delay(self) return publish_task.delay(self)
@classmethod
def gen_test_msg(cls):
raise NotImplementedError
def publish(self): def publish(self):
raise NotImplementedError raise NotImplementedError
@ -80,31 +86,46 @@ class Message(metaclass=MessageType):
continue continue
get_msg_method = getattr(self, f'get_{backend}_msg', self.get_common_msg) get_msg_method = getattr(self, f'get_{backend}_msg', self.get_common_msg)
try: try:
msg = get_msg_method() msg = get_msg_method()
except NotImplementedError: except NotImplementedError:
continue continue
client = backend.client() client = backend.client()
client.send_msg(users, **msg) client.send_msg(users, **msg)
except: except Exception:
traceback.print_exc() 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 from users.models import User
users = User.objects.filter(username='admin') 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: @staticmethod
raise NotImplementedError def get_common_msg() -> dict:
return {
def get_text_msg(self) -> dict: 'subject': '',
return self.common_msg 'message': ''
}
def get_html_msg(self) -> dict: 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 @lazyproperty
def common_msg(self) -> dict: def common_msg(self) -> dict:
@ -123,7 +144,8 @@ class Message(metaclass=MessageType):
def get_dingtalk_msg(self) -> dict: def get_dingtalk_msg(self) -> dict:
# 钉钉相同的消息一天只能发一次,所以给所有消息添加基于时间的序号,使他们不相同 # 钉钉相同的消息一天只能发一次,所以给所有消息添加基于时间的序号,使他们不相同
message = self.text_msg['message'] 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 { return {
'subject': self.text_msg['subject'], 'subject': self.text_msg['subject'],
@ -144,7 +166,25 @@ class Message(metaclass=MessageType):
def get_sms_msg(self) -> dict: def get_sms_msg(self) -> dict:
return self.text_msg 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): class SystemMessage(Message):
@ -161,13 +201,16 @@ class SystemMessage(Message):
*subscription.users.all(), *subscription.users.all(),
*chain(*[g.users.all() for g in subscription.groups.all()]) *chain(*[g.users.all() for g in subscription.groups.all()])
] ]
self.send_msg(users, receive_backends) self.send_msg(users, receive_backends)
@classmethod @classmethod
def post_insert_to_db(cls, subscription: SystemMsgSubscription): def post_insert_to_db(cls, subscription: SystemMsgSubscription):
pass pass
@classmethod
def gen_test_msg(cls):
raise NotImplementedError
class UserMessage(Message): class UserMessage(Message):
user: User user: User
@ -179,7 +222,9 @@ class UserMessage(Message):
""" """
发送消息到每个用户配置的接收方式上 发送消息到每个用户配置的接收方式上
""" """
sub = UserMsgSubscription.objects.get(user=self.user) sub = UserMsgSubscription.objects.get(user=self.user)
self.send_msg([self.user], sub.receive_backends) 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.models import F
from django.db import transaction 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 common.utils import get_logger
from users.models import User from users.models import User
from .models import SiteMessage as SiteMessageModel, SiteMessageUsers from .models import SiteMessage as SiteMessageModel, SiteMessageUsers
@ -88,7 +88,7 @@ class SiteMessageUtil:
for site_msg_user in site_msg_users: for site_msg_user in site_msg_users:
site_msg_user.has_read = True site_msg_user.has_read = True
site_msg_user.read_at = now() site_msg_user.read_at = local_now()
SiteMessageUsers.objects.bulk_update( SiteMessageUsers.objects.bulk_update(
site_msg_users, fields=('has_read', 'read_at')) site_msg_users, fields=('has_read', 'read_at'))

View File

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

View File

@ -24,13 +24,6 @@ class ServerPerformanceMessage(SystemMessage):
'message': self._msg '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 @classmethod
def post_insert_to_db(cls, subscription: SystemMsgSubscription): def post_insert_to_db(cls, subscription: SystemMsgSubscription):
admins = User.objects.filter(role=User.ROLE.ADMIN) admins = User.objects.filter(role=User.ROLE.ADMIN)
@ -38,6 +31,20 @@ class ServerPerformanceMessage(SystemMessage):
subscription.receive_backends = [BACKEND.EMAIL] subscription.receive_backends = [BACKEND.EMAIL]
subscription.save() 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): class ServerPerformanceCheckUtil(object):
items_mapper = { items_mapper = {
@ -50,21 +57,21 @@ class ServerPerformanceCheckUtil(object):
'default': 0, 'default': 0,
'max_threshold': 80, 'max_threshold': 80,
'alarm_msg_format': _( 'alarm_msg_format': _(
'[Disk] Disk used more than {max_threshold}%: => {value} ({name})' 'Disk used more than {max_threshold}%: => {value} ({name})'
) )
}, },
'memory_used': { 'memory_used': {
'default': 0, 'default': 0,
'max_threshold': 85, 'max_threshold': 85,
'alarm_msg_format': _( 'alarm_msg_format': _(
'[Memory] Memory used more than {max_threshold}%: => {value} ({name})' 'Memory used more than {max_threshold}%: => {value} ({name})'
), ),
}, },
'cpu_load': { 'cpu_load': {
'default': 0, 'default': 0,
'max_threshold': 5, 'max_threshold': 5,
'alarm_msg_format': _( '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): def ready(self):
super().ready() super().ready()
from . import signals_handler 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.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 as js_reverse
from notifications.notifications import UserMessage, SystemMessage 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): def __init__(self, user, assets):
super().__init__(user) super().__init__(user)
self.assets = assets self.assets = assets
def get_html_msg(self) -> dict: def get_html_msg(self) -> dict:
user = self.user subject = _("You permed assets is about to expire")
subject = _('Assets may expire') context = {
'name': self.user.name,
assets_text = ','.join(str(asset) for asset in self.assets) 'items': [str(asset) for asset in self.assets],
message = _(""" 'item_type': _("permed assets"),
Hello %(name)s: 'show_help': True
<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
} }
message = render_to_string('perms/_msg_permed_items_expire.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message 'message': message
} }
def get_text_msg(self) -> dict: @classmethod
user = self.user def gen_test_msg(cls):
subject = _('Assets may expire') from users.models import User
assets_text = ','.join(str(asset) for asset in self.assets) from assets.models import Asset
user = User.objects.first()
message = _(""" assets = Asset.objects.all()[:10]
Hello %(name)s: return cls(user, assets)
\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
}
class AssetPermWillExpireForOrgAdminMsg(UserMessage): class AssetPermsWillExpireForOrgAdminMsg(BasePermMsg):
def __init__(self, user, perms, org): def __init__(self, user, perms, org):
super().__init__(user) super().__init__(user)
self.perms = perms self.perms = perms
self.org = org self.org = org
def get_html_msg(self) -> dict: def get_items_with_url(self):
user = self.user items_with_url = []
subject = _('Asset permission will expired') for perm in self.perms:
perms_text = ','.join(str(perm) 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 = _(""" def get_html_msg(self):
Hello %(name)s: items_with_url = self.get_items_with_url()
<br> subject = _("Asset permissions is about to expire")
The following asset permissions of organization %(org) will expire in three days context = {
<br> 'name': self.user.name,
%(perms)s 'items_with_url': items_with_url,
""") % { 'item_type': _('asset permissions of organization {}').format(self.org)
'name': user.name,
'org': self.org,
'perms': perms_text
} }
message = render_to_string('perms/_msg_item_permissions_expire.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message 'message': message
} }
def get_text_msg(self) -> dict: @classmethod
user = self.user def gen_test_msg(cls):
subject = _('Asset permission will expired') from users.models import User
perms_text = ','.join(str(perm) for perm in self.perms) from perms.models import AssetPermission
from orgs.models import Organization
message = _(""" user = User.objects.first()
Hello %(name)s: perms = AssetPermission.objects.all()[:10]
\n org = Organization.objects.first()
The following asset permissions of organization %(org) will expire in three days return cls(user, perms, org)
\n
%(perms)s
""") % {
'name': user.name,
'org': self.org,
'perms': perms_text
}
return {
'subject': subject,
'message': message
}
class AssetPermWillExpireForAdminMsg(UserMessage): class PermedAppsWillExpireUserMsg(BasePermMsg):
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):
def __init__(self, user, apps): def __init__(self, user, apps):
super().__init__(user) super().__init__(user)
self.apps = apps self.apps = apps
def get_html_msg(self) -> dict: def get_html_msg(self) -> dict:
user = self.user subject = _("Your permed applications is about to expire")
subject = _('Applications may expire') context = {
'name': self.user.name,
apps_text = ','.join(str(app) for app in self.apps) 'item_type': _('permed applications'),
message = _(""" 'items': [str(app) for app in self.apps]
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
} }
message = render_to_string('perms/_msg_permed_items_expire.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message 'message': message
} }
def get_text_msg(self) -> dict: @classmethod
user = self.user def gen_test_msg(cls):
subject = _('Applications may expire') from users.models import User
apps_text = ','.join(str(app) for app in self.apps) from applications.models import Application
message = _(""" user = User.objects.first()
Hello %(name)s: apps = Application.objects.all()[:10]
\n return cls(user, apps)
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
}
class AppPermWillExpireForOrgAdminMsg(UserMessage): class AppPermsWillExpireForOrgAdminMsg(BasePermMsg):
def __init__(self, user, perms, org): def __init__(self, user, perms, org):
super().__init__(user) super().__init__(user)
self.perms = perms self.perms = perms
self.org = org self.org = org
def get_html_msg(self) -> dict: def get_items_with_url(self):
user = self.user items_with_url = []
subject = _('Application permission will expired') for perm in self.perms:
perms_text = ','.join(str(perm) for perm in self.perms) url = js_reverse(
'perms:application-permission-detail',
message = _(""" kwargs={'pk': perm.id}, external=True,
Hello %(name)s: api_to_ui=True
<br> ) + f'?oid={perm.org_id}'
The following application permissions of organization %(org) will expire in three days items_with_url.append([perm.name, url])
<br> return items_with_url
%(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_html_msg(self) -> dict: def get_html_msg(self) -> dict:
user = self.user items = self.get_items_with_url()
subject = _('Application permission will expired') subject = _('Application permissions is about to expire')
context = {
content = '' 'name': self.user.name,
for org, perms in self.org_perm_mapper.items(): 'item_type': _('application permissions of organization {}').format(self.org),
content += f'<br>Orgnization: {org} <br> Permissions: {",".join(str(perm) for perm in perms)} <br>' 'items_with_url': items
message = _("""
Hello %(name)s:
<br>
The following application permissions will expire in three days
<br>
%(content)s
""") % {
'name': user.name,
'content': content,
} }
message = render_to_string('perms/_msg_item_permissions_expire.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message 'message': message
} }
def get_text_msg(self) -> dict: @classmethod
user = self.user def gen_test_msg(cls):
subject = _('Application permission will expired') from users.models import User
from perms.models import ApplicationPermission
from orgs.models import Organization
content = '' user = User.objects.first()
for org, perms in self.org_perm_mapper.items(): perms = ApplicationPermission.objects.all()[:10]
content += f'\n Orgnization: {org} \n Permissions: {perms} \n' org = Organization.objects.first()
return cls(user, perms, org)
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
}

View File

@ -7,14 +7,13 @@ from django.db.transaction import atomic
from django.conf import settings from django.conf import settings
from celery import shared_task from celery import shared_task
from users.models import User
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
from common.utils import get_logger 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 ops.celery.decorator import register_as_period_task
from perms.notifications import ( from perms.notifications import (
AssetPermWillExpireMsg, AssetPermWillExpireForOrgAdminMsg, AssetPermWillExpireForAdminMsg, PermedWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg,
AppPermWillExpireMsg, AppPermWillExpireForOrgAdminMsg, AppPermWillExpireForAdminMsg, PermedAppsWillExpireUserMsg, AppPermsWillExpireForOrgAdminMsg
) )
from perms.models import AssetPermission, ApplicationPermission from perms.models import AssetPermission, ApplicationPermission
from perms.utils.asset.user_permission import UserGrantedTreeRefreshController from perms.utils.asset.user_permission import UserGrantedTreeRefreshController
@ -34,10 +33,10 @@ def check_asset_permission_expired():
setting_name = 'last_asset_perm_expired_check' setting_name = 'last_asset_perm_expired_check'
end = now() end = local_now()
default_start = end - timedelta(days=36000) # Long long ago in china 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( setting, created = Setting.objects.get_or_create(
name=setting_name, defaults=defaults name=setting_name, defaults=defaults
) )
@ -45,7 +44,7 @@ def check_asset_permission_expired():
start = default_start start = default_start
else: else:
start = dt_parser(setting.value) start = dt_parser(setting.value)
setting.value = dt_formater(end) setting.value = dt_formatter(end)
setting.save() setting.save()
asset_perm_ids = AssetPermission.objects.filter( asset_perm_ids = AssetPermission.objects.filter(
@ -61,14 +60,15 @@ def check_asset_permission_expired():
@atomic() @atomic()
@tmp_to_root_org() @tmp_to_root_org()
def check_asset_permission_will_expired(): def check_asset_permission_will_expired():
start = now() start = local_now()
end = start + timedelta(days=3) end = start + timedelta(days=3)
user_asset_mapper = defaultdict(set) user_asset_mapper = defaultdict(set)
org_perm_mapper = defaultdict(set) org_perm_mapper = defaultdict(set)
asset_perms = AssetPermission.objects.filter( asset_perms = AssetPermission.objects.filter(
date_expired__gte=start, date_expired__lte=end date_expired__gte=start,
date_expired__lte=end
).distinct() ).distinct()
for asset_perm in asset_perms: for asset_perm in asset_perms:
@ -83,18 +83,12 @@ def check_asset_permission_will_expired():
user_asset_mapper[u].update(assets) user_asset_mapper[u].update(assets)
for user, assets in user_asset_mapper.items(): for user, assets in user_asset_mapper.items():
AssetPermWillExpireMsg(user, assets).publish_async() PermedWillExpireUserMsg(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()
for org, perms in org_perm_mapper.items(): 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: 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 * * *') @register_as_period_task(crontab='0 10 * * *')
@ -102,11 +96,12 @@ def check_asset_permission_will_expired():
@atomic() @atomic()
@tmp_to_root_org() @tmp_to_root_org()
def check_app_permission_will_expired(): def check_app_permission_will_expired():
start = now() start = local_now()
end = start + timedelta(days=3) end = start + timedelta(days=3)
app_perms = ApplicationPermission.objects.filter( app_perms = ApplicationPermission.objects.filter(
date_expired__gte=start, date_expired__lte=end date_expired__gte=start,
date_expired__lte=end
).distinct() ).distinct()
user_app_mapper = defaultdict(set) user_app_mapper = defaultdict(set)
@ -121,15 +116,9 @@ def check_app_permission_will_expired():
user_app_mapper[u].update(apps) user_app_mapper[u].update(apps)
for user, apps in user_app_mapper.items(): for user, apps in user_app_mapper.items():
AppPermWillExpireMsg(user, apps).publish_async() PermedAppsWillExpireUserMsg(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()
for org, perms in org_perm_mapper.items(): 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: 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") help_text=_("Allow terminal register, after all terminal setup, you should disable this for security")
) )
SECURITY_WATERMARK_ENABLED = serializers.BooleanField( SECURITY_WATERMARK_ENABLED = serializers.BooleanField(
required=True, label=_('Replay watermark'), required=True, label=_('Enable watermark'),
help_text=_('Enabled, the session replay contains watermark information') help_text=_('Enabled, the web session and replay contains watermark information')
) )
SECURITY_MAX_IDLE_TIME = serializers.IntegerField( SECURITY_MAX_IDLE_TIME = serializers.IntegerField(
min_value=1, max_value=99999, required=False, min_value=1, max_value=99999, required=False,

View File

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

View File

@ -1,70 +1,49 @@
from urllib.parse import urljoin from urllib.parse import urljoin
from django.conf import settings from django.conf import settings
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from . import const from . import const
from notifications.notifications import UserMessage from notifications.notifications import UserMessage
from common.utils import get_logger from common.utils import get_logger
from .models import Ticket
logger = get_logger(__file__) 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): class BaseTicketMessage(UserMessage):
title: ''
ticket: Ticket
content_title: str
@property @property
def subject(self): 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 @property
def ticket_detail_url(self): def ticket_detail_url(self):
return urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(self.ticket.id))) return urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(self.ticket.id)))
def get_text_msg(self) -> dict: def get_html_msg(self) -> dict:
message = """ context = dict(
{title}: {ticket_detail_url}
{body}
""".format(
title=self.content_title, title=self.content_title,
ticket_detail_url=self.ticket_detail_url, 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 { return {
'subject': self.subject, 'subject': self.subject,
'message': message 'message': message
} }
def get_html_msg(self) -> dict: @classmethod
message = EMAIL_TEMPLATE.format( def gen_test_msg(cls):
title=self.content_title, return cls(None)
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
}
class TicketAppliedToAssignee(BaseTicketMessage): class TicketAppliedToAssignee(BaseTicketMessage):
title = 'New Ticket - {} ({})' title = _('New Ticket - {} ({})')
def __init__(self, user, ticket): def __init__(self, user, ticket):
self.ticket = ticket self.ticket = ticket
@ -72,11 +51,21 @@ class TicketAppliedToAssignee(BaseTicketMessage):
@property @property
def content_title(self): 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): class TicketProcessedToApplicant(BaseTicketMessage):
title = 'Ticket has processed - {} ({})' title = _('Ticket has processed - {} ({})')
def __init__(self, user, ticket, processor): def __init__(self, user, ticket, processor):
self.ticket = ticket self.ticket = ticket
@ -86,3 +75,12 @@ class TicketProcessedToApplicant(BaseTicketMessage):
@property @property
def content_title(self): def content_title(self):
return _('Your ticket has been processed, processor - {}').format(str(self.processor)) 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): def ready(self):
from . import signals_handler from . import signals_handler
from . import notifications
super().ready() super().ready()

View File

@ -1,84 +1,42 @@
from datetime import datetime from datetime import datetime
from urllib.parse import urljoin from urllib.parse import urljoin
from django.utils import timezone
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings 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 from notifications.notifications import UserMessage
class ResetPasswordMsg(UserMessage): class ResetPasswordMsg(UserMessage):
def __init__(self, user): def __init__(self, user):
super().__init__(user) super().__init__(user)
self.reset_passwd_token = user.generate_reset_token() 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: def get_html_msg(self) -> dict:
user = self.user user = self.user
subject = _('Reset password') subject = _('Reset password')
message = _(""" context = {
Hello %(name)s: 'user': user,
<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,
'rest_password_url': reverse('authentication:reset-password', external=True), 'rest_password_url': reverse('authentication:reset-password', external=True),
'rest_password_token': self.reset_passwd_token, 'rest_password_token': self.reset_passwd_token,
'forget_password_url': reverse('authentication:forgot-password', external=True), 'forget_password_url': reverse('authentication:forgot-password', external=True),
'email': user.email,
'login_url': reverse('authentication:login', external=True), 'login_url': reverse('authentication:login', external=True),
} }
message = render_to_string('authentication/_msg_reset_password.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message 'message': message
} }
@classmethod
def gen_test_msg(cls):
from users.models import User
user = User.objects.first()
return cls(user)
class ResetPasswordSuccessMsg(UserMessage): class ResetPasswordSuccessMsg(UserMessage):
def __init__(self, user, request): def __init__(self, user, request):
@ -86,280 +44,119 @@ class ResetPasswordSuccessMsg(UserMessage):
self.ip_address = get_request_ip_or_data(request) self.ip_address = get_request_ip_or_data(request)
self.browser = get_request_user_agent(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: def get_html_msg(self) -> dict:
user = self.user user = self.user
subject = _('Reset password success') subject = _('Reset password success')
message = _(""" context = {
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>
""") % {
'name': user.name, 'name': user.name,
'ip_address': self.ip_address, 'ip_address': self.ip_address,
'browser': self.browser, 'browser': self.browser,
} }
message = render_to_string('authentication/_msg_rest_password_success.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message '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): class PasswordExpirationReminderMsg(UserMessage):
update_password_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=PasswordUpdate') 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: def get_html_msg(self) -> dict:
user = self.user user = self.user
subject = _('Password is about expire')
subject = _('Security notice') date_password_expired_local = timezone.localtime(user.date_password_expired)
message = _(""" date_password_expired = date_password_expired_local.strftime('%Y-%m-%d %H:%M:%S')
Hello %(name)s: context = {
<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>
""") % {
'name': user.name, 'name': user.name,
'date_password_expired': datetime.fromtimestamp(datetime.timestamp( 'date_password_expired': date_password_expired,
user.date_password_expired)).strftime('%Y-%m-%d %H:%M'),
'update_password_url': self.update_password_url, 'update_password_url': self.update_password_url,
'forget_password_url': reverse('authentication:forgot-password', external=True), 'forget_password_url': reverse('authentication:forgot-password', external=True),
'email': user.email, 'email': user.email,
'login_url': reverse('authentication:login', external=True), 'login_url': reverse('authentication:login', external=True),
} }
message = render_to_string('users/_msg_password_expire_reminder.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message '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): 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: def get_html_msg(self) -> dict:
subject = _('Expiration notice') subject = _('Account is about expire')
message = _(""" date_expired_local = timezone.localtime(self.user.date_password_expired)
Hello %(name)s: date_expired = date_expired_local.strftime('%Y-%m-%d %H:%M:%S')
<br> context = {
Your account will expire in %(date_expired)s, 'name': self.user.name,
<br> 'date_expired': date_expired
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'),
} }
message = render_to_string('users/_msg_account_expire_reminder.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message '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): 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: def get_html_msg(self) -> dict:
subject = _('SSH Key Reset') subject = _('Reset SSH Key')
message = _(""" context = {
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>
""") % {
'name': self.user.name, 'name': self.user.name,
'login_url': reverse('authentication:login', external=True), 'login_url': reverse('authentication:login', external=True),
} }
message = render_to_string('users/_msg_reset_ssh_key.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message '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): 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: def get_html_msg(self) -> dict:
subject = _('MFA Reset') subject = _('Reset MFA')
message = _(""" context = {
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>
""") % {
'name': self.user.name, 'name': self.user.name,
'login_url': reverse('authentication:login', external=True), 'login_url': reverse('authentication:login', external=True),
} }
message = render_to_string('users/_msg_reset_mfa.html', context)
return { return {
'subject': subject, 'subject': subject,
'message': message '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() title, describe = self.get_title_describe()
context = { context = {
'title': title, 'title': title,
'messages': describe, 'message': describe,
'interval': 1, 'interval': 1,
'redirect_url': reverse('authentication:login'), 'redirect_url': reverse('authentication:login'),
'auto_redirect': True, 'auto_redirect': True,

View File

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