From 00d434ceea89f2231d50227471d4a63bf6b66678 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 20 Oct 2021 19:45:37 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=20(#7024)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 优化系统用户列表 * stash * perf: 优化消息通知 * perf: 修改钉钉 * perf: 修改优化消息通知 * perf: 修改requirements * perf: 优化datetime Co-authored-by: ibuler --- apps/acls/models/login_acl.py | 2 - apps/assets/serializers/system_user.py | 4 +- apps/authentication/api/sso.py | 4 +- apps/authentication/apps.py | 1 + apps/authentication/notifications.py | 70 +- .../authentication/_msg_different_city.html | 18 + .../authentication/_msg_reset_password.html | 15 + .../_msg_rest_password_success.html | 18 + apps/authentication/views/dingtalk.py | 2 +- apps/authentication/views/login.py | 2 +- apps/common/utils/time_period.py | 6 +- apps/common/utils/timezone.py | 8 +- apps/locale/zh/LC_MESSAGES/django.mo | 3 + apps/locale/zh/LC_MESSAGES/django.po | 1347 +++++------------ apps/notifications/apps.py | 1 + apps/notifications/notifications.py | 83 +- apps/notifications/site_msg.py | 4 +- apps/ops/celery/utils.py | 4 +- apps/ops/notifications.py | 27 +- apps/perms/apps.py | 1 + apps/perms/notifications.py | 343 ++--- apps/perms/tasks.py | 47 +- .../perms/_msg_item_permissions_expire.html | 16 + .../perms/_msg_permed_items_expire.html | 24 + apps/settings/serializers/security.py | 4 +- apps/terminal/notifications.py | 108 +- .../terminal/_msg_command_alert.html | 17 + .../terminal/_msg_command_execute_alert.html | 24 + apps/tickets/apps.py | 1 + apps/tickets/notifications.py | 72 +- .../templates/tickets/_msg_ticket.html | 14 + apps/users/apps.py | 1 + apps/users/notifications.py | 337 +---- .../users/_msg_account_expire_reminder.html | 9 + .../users/_msg_password_expire_reminder.html | 22 + .../users/templates/users/_msg_reset_mfa.html | 12 + .../templates/users/_msg_reset_ssh_key.html | 12 + apps/users/views/profile/otp.py | 2 +- requirements/requirements.txt | 1 + 39 files changed, 948 insertions(+), 1738 deletions(-) create mode 100644 apps/authentication/templates/authentication/_msg_different_city.html create mode 100644 apps/authentication/templates/authentication/_msg_reset_password.html create mode 100644 apps/authentication/templates/authentication/_msg_rest_password_success.html create mode 100644 apps/locale/zh/LC_MESSAGES/django.mo create mode 100644 apps/perms/templates/perms/_msg_item_permissions_expire.html create mode 100644 apps/perms/templates/perms/_msg_permed_items_expire.html create mode 100644 apps/terminal/templates/terminal/_msg_command_alert.html create mode 100644 apps/terminal/templates/terminal/_msg_command_execute_alert.html create mode 100644 apps/tickets/templates/tickets/_msg_ticket.html create mode 100644 apps/users/templates/users/_msg_account_expire_reminder.html create mode 100644 apps/users/templates/users/_msg_password_expire_reminder.html create mode 100644 apps/users/templates/users/_msg_reset_mfa.html create mode 100644 apps/users/templates/users/_msg_reset_ssh_key.html diff --git a/apps/acls/models/login_acl.py b/apps/acls/models/login_acl.py index f57c81a3c..bf69c1018 100644 --- a/apps/acls/models/login_acl.py +++ b/apps/acls/models/login_acl.py @@ -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 '' diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 588aedfc4..16cf9451c 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -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 diff --git a/apps/authentication/api/sso.py b/apps/authentication/api/sso.py index 66febfbd7..11435edcb 100644 --- a/apps/authentication/api/sso.py +++ b/apps/authentication/api/sso.py @@ -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) diff --git a/apps/authentication/apps.py b/apps/authentication/apps.py index 0869b64fd..5b63e5696 100644 --- a/apps/authentication/apps.py +++ b/apps/authentication/apps.py @@ -6,5 +6,6 @@ class AuthenticationConfig(AppConfig): def ready(self): from . import signals_handlers + from . import notifications super().ready() diff --git a/apps/authentication/notifications.py b/apps/authentication/notifications.py index cad3c0f2c..5c5f8de2f 100644 --- a/apps/authentication/notifications.py +++ b/apps/authentication/notifications.py @@ -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 = _( - "" - "

{subject}

" - "

Dear {server_name} user, Hello!

" - "

Your account has remote login behavior, please pay attention.

" - "

User: {username}

" - "

Login time: {time}

" - "

Login location: {city} ({ip})

" - "

If you suspect that the login behavior is abnormal, please modify " - "

the account password in time.

" - "
" - "

Thank you for your attention to {server_name}!

") - 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) diff --git a/apps/authentication/templates/authentication/_msg_different_city.html b/apps/authentication/templates/authentication/_msg_different_city.html new file mode 100644 index 000000000..16ff90fbc --- /dev/null +++ b/apps/authentication/templates/authentication/_msg_different_city.html @@ -0,0 +1,18 @@ +{% load i18n %} +

+ {% trans 'Hello' %} {{ name }}, +

+

+ {% trans 'Your account has remote login behavior, please pay attention' %} +

+

+ {% trans 'Username' %}: {{ username }}
+ {% trans 'Login time' %}: {{ time }}
+ {% trans 'Login city' %}: {{ city }}({{ ip }}) +

+ +

+ + {% trans 'If you suspect that the login behavior is abnormal, please modify the account password in time.' %} + +

\ No newline at end of file diff --git a/apps/authentication/templates/authentication/_msg_reset_password.html b/apps/authentication/templates/authentication/_msg_reset_password.html new file mode 100644 index 000000000..9388f2388 --- /dev/null +++ b/apps/authentication/templates/authentication/_msg_reset_password.html @@ -0,0 +1,15 @@ +{% load i18n %} +{% trans 'Hello' %} {{ user.name }}, +
+{% trans 'Please click the link below to reset your password, if not your request, concern your account security' %} +
+
+{% trans 'Click here reset password' %} +
+
+{% trans 'This link is valid for 1 hour. After it expires,' %} {% trans 'request new one' %} +
+--- +
+{% trans 'Login direct' %} +
diff --git a/apps/authentication/templates/authentication/_msg_rest_password_success.html b/apps/authentication/templates/authentication/_msg_rest_password_success.html new file mode 100644 index 000000000..424396473 --- /dev/null +++ b/apps/authentication/templates/authentication/_msg_rest_password_success.html @@ -0,0 +1,18 @@ +{% load i18n %} + +

{% trans 'Hello' %}: {{ name }},

+ +

+ {% trans 'Your password has just been successfully updated.' %} +

+

+ {% trans 'IP' %}: {{ ip_address }}
+ {% trans 'Browser' %}: {{ browser }} +

+

---

+

+ + {% trans 'If the password update was not initiated by you, your account may have security issues.' %}
+ {% trans 'If you have any questions, you can contact the administrator.' %} +
+

diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index 34a25454b..630125151 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -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) diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index 42dba54e7..c39f9b7ec 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -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, diff --git a/apps/common/utils/time_period.py b/apps/common/utils/time_period.py index 8a009a7f9..c177e0fb6 100644 --- a/apps/common/utils/time_period.py +++ b/apps/common/utils/time_period.py @@ -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 diff --git a/apps/common/utils/timezone.py b/apps/common/utils/timezone.py index 2b8779bae..037aad62c 100644 --- a/apps/common/utils/timezone.py +++ b/apps/common/utils/timezone.py @@ -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 diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo new file mode 100644 index 000000000..c16f2930a --- /dev/null +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39e9d8c61c6986a067d9c0c82d2a459e07bcc78d07d9bdd1c5934154ea3a198d +size 91321 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index f154038ef..2987511f3 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-10-18 17:36+0800\n" +"POT-Creation-Date: 2021-10-20 18:58+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -97,6 +97,8 @@ msgstr "动作" #: perms/models/base.py:45 templates/index.html:78 #: terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 +#: terminal/templates/terminal/_msg_command_alert.html:10 +#: terminal/templates/terminal/_msg_command_execue_alert.html:4 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:180 #: users/models/user.py:813 users/models/user.py:839 #: users/serializers/group.py:19 @@ -119,10 +121,11 @@ msgstr "系统用户" #: acls/models/login_asset_acl.py:22 #: applications/serializers/attrs/application_category/remote_app.py:37 #: assets/models/asset.py:357 assets/models/authbook.py:18 -#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:207 +#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:215 #: audits/models.py:38 perms/models/asset_permission.py:99 #: templates/index.html:82 terminal/backends/command/models.py:19 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:40 +#: terminal/templates/terminal/_msg_command_alert.html:7 #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 #: xpack/plugins/change_auth_plan/models/asset.py:195 @@ -155,7 +158,9 @@ msgstr "" #: acls/serializers/login_acl.py:30 acls/serializers/login_asset_acl.py:31 #: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: assets/models/asset.py:180 assets/models/domain.py:61 -#: assets/serializers/account.py:12 settings/serializers/terminal.py:8 +#: assets/serializers/account.py:12 +#: authentication/templates/authentication/_msg_rest_password_success.html:9 +#: settings/serializers/terminal.py:8 #: users/templates/users/_granted_assets.html:26 #: users/templates/users/user_asset_permission.html:156 msgid "IP" @@ -177,6 +182,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: applications/serializers/attrs/application_type/vmware_client.py:26 #: assets/models/base.py:176 assets/models/gathered_user.py:15 #: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17 +#: authentication/templates/authentication/_msg_different_city.html:9 #: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:602 #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models/asset.py:35 @@ -316,7 +322,7 @@ msgstr "类别名称" #: applications/serializers/application.py:60 #: applications/serializers/application.py:91 -#: assets/serializers/system_user.py:26 audits/serializers.py:29 +#: assets/serializers/system_user.py:27 audits/serializers.py:29 #: perms/serializers/application/permission.py:17 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:35 #: tickets/serializers/ticket/ticket.py:22 @@ -421,7 +427,7 @@ msgstr "基础" msgid "Charset" msgstr "编码" -#: assets/models/asset.py:142 assets/serializers/asset.py:172 +#: assets/models/asset.py:142 assets/serializers/asset.py:174 #: tickets/models/ticket.py:50 msgid "Meta" msgstr "元数据" @@ -431,11 +437,11 @@ msgid "Internal" msgstr "内部的" #: assets/models/asset.py:163 assets/models/asset.py:187 -#: assets/serializers/asset.py:63 perms/serializers/asset/user_permission.py:43 +#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:43 msgid "Platform" msgstr "系统平台" -#: assets/models/asset.py:186 assets/serializers/asset.py:65 +#: assets/models/asset.py:186 assets/serializers/asset.py:67 #: perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" @@ -643,6 +649,8 @@ msgstr "正则表达式" #: assets/models/cmd_filter.py:41 ops/models/command.py:25 #: terminal/backends/command/serializers.py:15 terminal/models/session.py:49 +#: terminal/templates/terminal/_msg_command_alert.html:4 +#: terminal/templates/terminal/_msg_command_execue_alert.html:15 msgid "Command" msgstr "命令" @@ -740,7 +748,7 @@ msgstr "全称" msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:559 assets/serializers/system_user.py:206 +#: assets/models/node.py:559 assets/serializers/system_user.py:214 #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 @@ -764,8 +772,10 @@ msgstr "普通用户" msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/user.py:200 assets/serializers/domain.py:28 -#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models/asset.py:40 +#: assets/models/user.py:200 assets/serializers/domain.py:29 +#: templates/_nav.html:39 +#: terminal/templates/terminal/_msg_command_execue_alert.html:10 +#: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "资产" @@ -819,32 +829,32 @@ msgstr "%(value)s is not an even number" msgid "System user display" msgstr "系统用户名称" -#: assets/serializers/asset.py:20 +#: assets/serializers/asset.py:22 msgid "Protocol format should {}/{}" msgstr "协议格式 {}/{}" -#: assets/serializers/asset.py:37 +#: assets/serializers/asset.py:39 msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset.py:66 +#: assets/serializers/asset.py:68 msgid "Domain name" msgstr "网域名称" -#: assets/serializers/asset.py:67 +#: assets/serializers/asset.py:69 msgid "Nodes name" msgstr "节点名称" -#: assets/serializers/asset.py:101 +#: assets/serializers/asset.py:103 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:102 assets/serializers/system_user.py:225 +#: assets/serializers/asset.py:104 assets/serializers/system_user.py:233 #: orgs/mixins/serializers.py:26 msgid "Org name" msgstr "组织名称" -#: assets/serializers/asset.py:103 +#: assets/serializers/asset.py:105 msgid "Admin user display" msgstr "特权用户名称" @@ -852,18 +862,18 @@ msgstr "特权用户名称" msgid "private key invalid" msgstr "密钥不合法" -#: assets/serializers/domain.py:12 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:52 +#: assets/serializers/domain.py:13 assets/serializers/label.py:12 +#: assets/serializers/system_user.py:53 #: perms/serializers/asset/permission.py:72 msgid "Assets amount" msgstr "资产数量" -#: assets/serializers/domain.py:13 +#: assets/serializers/domain.py:14 #: perms/serializers/application/permission.py:43 msgid "Applications amount" msgstr "应用数量" -#: assets/serializers/domain.py:14 +#: assets/serializers/domain.py:15 msgid "Gateways count" msgstr "网关数量" @@ -879,48 +889,52 @@ msgstr "不能包含: /" msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/system_user.py:27 +#: assets/serializers/system_user.py:28 msgid "SSH key fingerprint" msgstr "密钥指纹" -#: assets/serializers/system_user.py:51 +#: assets/serializers/system_user.py:52 #: perms/serializers/asset/permission.py:73 msgid "Nodes amount" msgstr "节点数量" -#: assets/serializers/system_user.py:53 assets/serializers/system_user.py:208 +#: assets/serializers/system_user.py:54 assets/serializers/system_user.py:216 msgid "Login mode display" msgstr "认证方式名称" -#: assets/serializers/system_user.py:55 +#: assets/serializers/system_user.py:56 msgid "Ad domain" msgstr "Ad 网域" -#: assets/serializers/system_user.py:56 +#: assets/serializers/system_user.py:57 msgid "Is asset protocol" msgstr "" -#: assets/serializers/system_user.py:96 +#: assets/serializers/system_user.py:97 msgid "Username same with user with protocol {} only allow 1" msgstr "用户名和用户相同的一种协议只允许存在一个" -#: assets/serializers/system_user.py:110 +#: assets/serializers/system_user.py:107 common/validators.py:14 +msgid "Special char not allowed" +msgstr "不能包含特殊字符" + +#: assets/serializers/system_user.py:116 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/serializers/system_user.py:124 +#: assets/serializers/system_user.py:130 msgid "Path should starts with /" msgstr "路径应该以 / 开头" -#: assets/serializers/system_user.py:149 +#: assets/serializers/system_user.py:155 msgid "Password or private key required" msgstr "密码或密钥密码需要一个" -#: assets/serializers/system_user.py:224 +#: assets/serializers/system_user.py:232 msgid "System user name" msgstr "系统用户名称" -#: assets/serializers/system_user.py:234 +#: assets/serializers/system_user.py:242 msgid "Asset hostname" msgstr "资产主机名" @@ -1150,6 +1164,7 @@ msgid "Login ip" msgstr "登录IP" #: audits/models.py:108 +#: authentication/templates/authentication/_msg_different_city.html:11 #: tickets/serializers/ticket/meta/ticket_type/login_confirm.py:17 msgid "Login city" msgstr "登录城市" @@ -1639,10 +1654,8 @@ msgid "Your password is invalid" msgstr "您的密码无效" #: authentication/errors.py:357 -#, fuzzy -#| msgid "Not has host {} permission" msgid "No upload or download permission" -msgstr "没有该主机 {} 权限" +msgstr "没有上传下载权限" #: authentication/forms.py:35 msgid "{} days auto login" @@ -1668,6 +1681,10 @@ msgstr "动态码" msgid "Please change your password" msgstr "请修改密码" +#: authentication/mixins.py:514 +msgid "SMS" +msgstr "短信" + #: authentication/models.py:40 msgid "Private Token" msgstr "SSH密钥" @@ -1684,47 +1701,10 @@ msgstr "{} 需要 {} 复核" msgid "Expired" msgstr "过期时间" -#: authentication/notifications.py:11 -#, python-brace-format -msgid "" -"

{subject}

Dear {server_name} user, Hello!

Your account has " -"remote login behavior, please pay attention.

User: {username}

Login time: {time}

Login location: {city} ({ip})

If you " -"suspect that the login behavior is abnormal, please modify

the account " -"password in time.


Thank you for your attention to {server_name}!" -msgstr "" -"

{subject}

尊敬的{server_name}用户,   您好!

您的账" -"号存在异地登录行为,请关注。

用户: {username}

登录时间: {time}

登录地点: {city} ({ip})

若怀疑此次登录行为异常,请及时修改账号密" -"码。


感谢您对{server_name}的关注!

" - -#: authentication/notifications.py:36 +#: authentication/notifications.py:19 msgid "Different city login reminder" msgstr "异地登录提醒" -#: authentication/notifications.py:40 -#, python-brace-format -msgid "" -"{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" -msgstr "" -"{subject}\n" -"尊敬的{server_name}用户, 您好!\n" -"您的账号存在异地登录行为,请关注。\n" -"用户: {username}\n" -"登录时间: {time}\n" -"登录地点: {city} ({ip})\n" -"若怀疑此次登录行为异常,请及时修改账号密码。\n" -"感谢您对{server_name}的关注!\n" - #: authentication/sms_verify_code.py:15 msgid "The verification code has expired. Please resend it" msgstr "验证码已过期,请重新发送" @@ -1816,6 +1796,75 @@ msgstr "确认" msgid "Code error" msgstr "代码错误" +#: authentication/templates/authentication/_msg_different_city.html:3 +#: authentication/templates/authentication/_msg_reset_password.html:2 +#: authentication/templates/authentication/_msg_rest_password_success.html:3 +#: perms/templates/perms/_msg_item_permissions_expire.html:3 +#: perms/templates/perms/_msg_permed_items_expire.html:3 +#: users/templates/users/_msg_account_expire_reminder.html:4 +#: users/templates/users/_msg_password_expire_reminder.html:4 +#: users/templates/users/_msg_reset_mfa.html:4 +#: users/templates/users/_msg_reset_ssh_key.html:4 +msgid "Hello" +msgstr "你好" + +#: authentication/templates/authentication/_msg_different_city.html:6 +msgid "Your account has remote login behavior, please pay attention" +msgstr "你的账号存在异地登录行为,请关注。" + +#: authentication/templates/authentication/_msg_different_city.html:10 +msgid "Login time" +msgstr "登录日期" + +#: authentication/templates/authentication/_msg_different_city.html:16 +msgid "" +"If you suspect that the login behavior is abnormal, please modify the " +"account password in time." +msgstr "若怀疑此次登录行为异常,请及时修改账号密码" + +#: authentication/templates/authentication/_msg_reset_password.html:4 +msgid "" +"Please click the link below to reset your password, if not your request, " +"concern your account security" +msgstr "请点击下面链接重置密码, 如果不是您申请的,请关注账号安全" + +#: authentication/templates/authentication/_msg_reset_password.html:7 +msgid "Click here reset password" +msgstr "点击这里重置密码" + +#: authentication/templates/authentication/_msg_reset_password.html:10 +msgid "This link is valid for 1 hour. After it expires," +msgstr "这个链接有效期1小时, 超过时间您可以" + +#: authentication/templates/authentication/_msg_reset_password.html:10 +msgid "request new one" +msgstr "重新申请" + +#: authentication/templates/authentication/_msg_reset_password.html:14 +#: users/templates/users/_msg_password_expire_reminder.html:22 +#: users/templates/users/_msg_reset_mfa.html:11 +#: users/templates/users/_msg_reset_ssh_key.html:11 +msgid "Login direct" +msgstr "直接登录" + +#: authentication/templates/authentication/_msg_rest_password_success.html:6 +msgid "Your password has just been successfully updated." +msgstr "你的密码刚刚已经成功更新。" + +#: authentication/templates/authentication/_msg_rest_password_success.html:10 +msgid "Browser" +msgstr "浏览器" + +#: authentication/templates/authentication/_msg_rest_password_success.html:15 +msgid "" +"If the password update was not initiated by you, your account may have " +"security issues." +msgstr "如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题。" + +#: authentication/templates/authentication/_msg_rest_password_success.html:16 +msgid "If you have any questions, you can contact the administrator." +msgstr "如果有疑问或需求,请联系系统管理员" + #: authentication/templates/authentication/login.html:143 msgid "Welcome back, please enter username and password to login" msgstr "欢迎回来,请输入用户名和密码登录" @@ -1863,98 +1912,89 @@ msgstr "返回" msgid "Copy success" msgstr "复制成功" -#: authentication/views/dingtalk.py:41 +#: authentication/views/dingtalk.py:37 msgid "DingTalk Error, Please contact your system administrator" msgstr "钉钉错误,请联系系统管理员" -#: authentication/views/dingtalk.py:44 +#: authentication/views/dingtalk.py:40 msgid "DingTalk Error" msgstr "钉钉错误" -#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:52 -#: authentication/views/wecom.py:56 -msgid "You've been hacked" -msgstr "你被攻击了" +#: authentication/views/dingtalk.py:52 authentication/views/feishu.py:48 +#: authentication/views/wecom.py:52 +msgid "" +"The system configuration is incorrect. Please contact your administrator" +msgstr "企业配置错误,请联系系统管理员" -#: authentication/views/dingtalk.py:92 +#: authentication/views/dingtalk.py:90 msgid "DingTalk is already bound" msgstr "钉钉已经绑定" -#: authentication/views/dingtalk.py:105 authentication/views/feishu.py:99 -#: authentication/views/wecom.py:104 +#: authentication/views/dingtalk.py:103 authentication/views/feishu.py:97 +#: authentication/views/wecom.py:102 msgid "Please verify your password first" msgstr "请检查密码" -#: authentication/views/dingtalk.py:129 authentication/views/wecom.py:128 +#: authentication/views/dingtalk.py:127 authentication/views/wecom.py:126 msgid "Invalid user_id" msgstr "无效的 user_id" -#: authentication/views/dingtalk.py:145 +#: authentication/views/dingtalk.py:143 msgid "DingTalk query user failed" msgstr "钉钉查询用户失败" -#: authentication/views/dingtalk.py:154 +#: authentication/views/dingtalk.py:152 msgid "The DingTalk is already bound to another user" msgstr "该钉钉已经绑定其他用户" -#: authentication/views/dingtalk.py:159 authentication/views/dingtalk.py:242 -#: authentication/views/dingtalk.py:243 +#: authentication/views/dingtalk.py:157 msgid "Binding DingTalk successfully" msgstr "绑定 钉钉 成功" -#: authentication/views/dingtalk.py:211 +#: authentication/views/dingtalk.py:209 msgid "Failed to get user from DingTalk" msgstr "从钉钉获取用户失败" -#: authentication/views/dingtalk.py:217 +#: authentication/views/dingtalk.py:215 msgid "DingTalk is not bound" msgstr "钉钉没有绑定" -#: authentication/views/dingtalk.py:218 +#: authentication/views/dingtalk.py:216 msgid "Please login with a password and then bind the DingTalk" msgstr "请使用密码登录,然后绑定钉钉" -#: authentication/views/dingtalk.py:260 authentication/views/dingtalk.py:261 -msgid "Binding DingTalk failed" -msgstr "绑定钉钉失败" - -#: authentication/views/feishu.py:40 +#: authentication/views/feishu.py:36 msgid "FeiShu Error" msgstr "飞书错误" -#: authentication/views/feishu.py:86 +#: authentication/views/feishu.py:84 msgid "FeiShu is already bound" msgstr "飞书已经绑定" -#: authentication/views/feishu.py:133 +#: authentication/views/feishu.py:131 msgid "FeiShu query user failed" msgstr "飞书查询用户失败" -#: authentication/views/feishu.py:142 +#: authentication/views/feishu.py:140 msgid "The FeiShu is already bound to another user" msgstr "该飞书已经绑定其他用户" -#: authentication/views/feishu.py:147 authentication/views/feishu.py:229 -#: authentication/views/feishu.py:230 +#: authentication/views/feishu.py:145 msgid "Binding FeiShu successfully" msgstr "绑定 飞书 成功" -#: authentication/views/feishu.py:198 +#: authentication/views/feishu.py:196 msgid "Failed to get user from FeiShu" msgstr "从飞书获取用户失败" -#: authentication/views/feishu.py:204 +#: authentication/views/feishu.py:202 msgid "FeiShu is not bound" msgstr "没有绑定飞书" -#: authentication/views/feishu.py:205 +#: authentication/views/feishu.py:203 msgid "Please login with a password and then bind the FeiShu" msgstr "请使用密码登录,然后绑定飞书" -#: authentication/views/feishu.py:247 authentication/views/feishu.py:248 -msgid "Binding FeiShu failed" -msgstr "绑定飞书失败" - #: authentication/views/login.py:82 msgid "Redirecting" msgstr "跳转中" @@ -1972,11 +2012,7 @@ msgstr "设置你的浏览器支持cookie" msgid "FeiShu" msgstr "飞书" -#: authentication/views/login.py:202 authentication/views/mfa.py:50 -msgid "SMS" -msgstr "短信" - -#: authentication/views/login.py:291 +#: authentication/views/login.py:269 msgid "" "Wait for {} confirm, You also can copy link to her/him
\n" " Don't close this page" @@ -1984,59 +2020,54 @@ msgstr "" "等待 {} 确认, 你也可以复制链接发给他/她
\n" " 不要关闭本页面" -#: authentication/views/login.py:296 +#: authentication/views/login.py:274 msgid "No ticket found" msgstr "没有发现工单" -#: authentication/views/login.py:328 +#: authentication/views/login.py:306 msgid "Logout success" msgstr "退出登录成功" -#: authentication/views/login.py:329 +#: authentication/views/login.py:307 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: authentication/views/wecom.py:41 +#: authentication/views/wecom.py:37 msgid "WeCom Error, Please contact your system administrator" msgstr "企业微信错误,请联系系统管理员" -#: authentication/views/wecom.py:44 +#: authentication/views/wecom.py:40 msgid "WeCom Error" msgstr "企业微信错误" -#: authentication/views/wecom.py:91 +#: authentication/views/wecom.py:89 msgid "WeCom is already bound" msgstr "企业微信已经绑定" -#: authentication/views/wecom.py:143 +#: authentication/views/wecom.py:141 msgid "WeCom query user failed" msgstr "企业微信查询用户失败" -#: authentication/views/wecom.py:152 +#: authentication/views/wecom.py:150 msgid "The WeCom is already bound to another user" msgstr "该企业微信已经绑定其他用户" -#: authentication/views/wecom.py:157 authentication/views/wecom.py:240 -#: authentication/views/wecom.py:241 +#: authentication/views/wecom.py:155 msgid "Binding WeCom successfully" msgstr "绑定 企业微信 成功" -#: authentication/views/wecom.py:209 +#: authentication/views/wecom.py:204 msgid "Failed to get user from WeCom" msgstr "从企业微信获取用户失败" -#: authentication/views/wecom.py:215 +#: authentication/views/wecom.py:210 msgid "WeCom is not bound" msgstr "没有绑定企业微信" -#: authentication/views/wecom.py:216 +#: authentication/views/wecom.py:211 msgid "Please login with a password and then bind the WeCom" msgstr "请使用密码登录,然后绑定企业微信" -#: authentication/views/wecom.py:258 authentication/views/wecom.py:259 -msgid "Binding WeCom failed" -msgstr "绑定企业微信失败" - #: common/const/__init__.py:6 #, python-format msgid "%(name)s was created successfully" @@ -2160,23 +2191,27 @@ msgstr "忽略的" msgid "discard time" msgstr "忽略时间" -#: common/utils/ipip/utils.py:15 +#: common/utils/geoip/utils.py:17 common/utils/geoip/utils.py:30 msgid "Invalid ip" msgstr "无效IP" -#: common/validators.py:15 -msgid "Special char not allowed" -msgstr "不能包含特殊字符" +#: common/utils/geoip/utils.py:28 +msgid "LAN" +msgstr "" -#: common/validators.py:27 +#: common/utils/geoip/utils.py:35 common/utils/geoip/utils.py:45 +msgid "Unknown ip" +msgstr "未知ip" + +#: common/validators.py:30 msgid "This field must be unique." msgstr "字段必须唯一" -#: common/validators.py:35 +#: common/validators.py:38 msgid "Should not contains special characters" msgstr "不能包含特殊字符" -#: common/validators.py:41 +#: common/validators.py:44 msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" @@ -2220,13 +2255,12 @@ msgstr "邮件" msgid "Site message" msgstr "站内信" -#: notifications/notifications.py:126 -msgid "" -"\n" -"Time: {}" -msgstr "" -"\n" -"时间:{}" +#: notifications/notifications.py:147 ops/models/adhoc.py:246 +#: xpack/plugins/change_auth_plan/models/base.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:190 +#: xpack/plugins/gathered_user/models.py:79 +msgid "Time" +msgstr "时间" #: ops/api/celery.py:61 ops/api/celery.py:76 msgid "Waiting task start" @@ -2329,12 +2363,6 @@ msgstr "开始时间" msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:246 xpack/plugins/change_auth_plan/models/base.py:108 -#: xpack/plugins/change_auth_plan/models/base.py:190 -#: xpack/plugins/gathered_user/models.py:79 -msgid "Time" -msgstr "时间" - #: ops/models/adhoc.py:247 ops/models/command.py:28 #: terminal/serializers/session.py:39 msgid "Is finished" @@ -2368,25 +2396,25 @@ msgstr "任务结束" msgid "Server performance" msgstr "监控告警" -#: ops/notifications.py:47 +#: ops/notifications.py:54 #, python-brace-format msgid "The terminal is offline: {name}" msgstr "终端已离线: {name}" -#: ops/notifications.py:53 -#, python-brace-format -msgid "[Disk] Disk used more than {max_threshold}%: => {value} ({name})" -msgstr "[Disk] 硬盘使用率超过 {max_threshold}%: => {value} ({name})" - #: ops/notifications.py:60 #, python-brace-format -msgid "[Memory] Memory used more than {max_threshold}%: => {value} ({name})" -msgstr "[Memory] 内存使用率超过 {max_threshold}%: => {value} ({name})" +msgid "Disk used more than {max_threshold}%: => {value} ({name})" +msgstr "硬盘使用率超过 {max_threshold}%: => {value} ({name})" #: ops/notifications.py:67 #, python-brace-format -msgid "[CPU] CPU load more than {max_threshold}: => {value} ({name})" -msgstr "[CPU] CPU 使用率超过 {max_threshold}: => {value} ({name})" +msgid "Memory used more than {max_threshold}%: => {value} ({name})" +msgstr "内存使用率超过 {max_threshold}%: => {value} ({name})" + +#: ops/notifications.py:74 +#, python-brace-format +msgid "CPU load more than {max_threshold}: => {value} ({name})" +msgstr "CPU 使用率超过 {max_threshold}: => {value} ({name})" #: ops/tasks.py:71 msgid "Clean task history period" @@ -2514,271 +2542,37 @@ msgstr "失效日期" msgid "From ticket" msgstr "来自工单" -#: perms/notifications.py:18 perms/notifications.py:40 -#, fuzzy -#| msgid "Asset number" -msgid "Assets may expire" -msgstr "资产编号" - #: perms/notifications.py:21 -#, fuzzy, python-format -#| msgid "" -#| "\n" -#| " Hello %(name)s:\n" -#| "
\n" -#| " Your account will expire in %(date_expired)s,\n" -#| "
\n" -#| " In order not to affect your normal work, please contact the " -#| "administrator for confirmation.\n" -#| "
\n" -#| " " -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" Your permissions for the following assets may expire in three " -"days:\n" -"
\n" -" %(assets)s\n" -"
\n" -" Please contact the administrator\n" -" " -msgstr "" -"\n" -" 您好 %(name)s:\n" -"
\n" -" 您的账户会在 %(date_expired)s 过期,\n" -"
\n" -" 为了不影响您正常工作,请联系管理员确认。\n" -"
\n" -" " +msgid "You permed assets is about to expire" +msgstr "你授权的资产即将到期" -#: perms/notifications.py:43 -#, python-format -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"\n" -"Your permissions for the following assets may expire in three days:\n" -"\n" -"\n" -"%(assets)s\n" -"\n" -"\n" -"Please contact the administrator\n" -" " -msgstr "" +#: perms/notifications.py:25 +msgid "permed assets" +msgstr "授权的资产" -#: perms/notifications.py:69 perms/notifications.py:90 -#: perms/notifications.py:117 perms/notifications.py:140 -#, fuzzy -#| msgid "Asset permission" -msgid "Asset permission will expired" -msgstr "资产授权" +#: perms/notifications.py:63 +msgid "Asset permissions is about to expire" +msgstr "资产授权规则将要过期" -#: perms/notifications.py:72 -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" The following asset permissions of organization %(org) will " -"expire in three days\n" -"
\n" -" %(perms)s\n" -" " -msgstr "" +#: perms/notifications.py:67 +msgid "asset permissions of organization {}" +msgstr "组织 ({}) 的资产授权" #: perms/notifications.py:93 -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"\n" -"The following asset permissions of organization %(org) will expire in three " -"days\n" -"\n" -"\n" -"%(perms)s\n" -" " -msgstr "" +msgid "Your permed applications is about to expire" +msgstr "你授权的应用即将过期" -#: perms/notifications.py:123 -#, fuzzy, python-format -#| msgid "" -#| "\n" -#| " Hello %(name)s:\n" -#| "
\n" -#| " Your account will expire in %(date_expired)s,\n" -#| "
\n" -#| " In order not to affect your normal work, please contact the " -#| "administrator for confirmation.\n" -#| "
\n" -#| " " -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" The following asset permissions will expire in three days\n" -"
\n" -" %(content)s\n" -" " -msgstr "" -"\n" -" 您好 %(name)s:\n" -"
\n" -" 您的账户会在 %(date_expired)s 过期,\n" -"
\n" -" 为了不影响您正常工作,请联系管理员确认。\n" -"
\n" -" " +#: perms/notifications.py:96 +msgid "permed applications" +msgstr "授权的应用" -#: perms/notifications.py:146 -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"\n" -"The following asset permissions of organization %(org) will expire in three " -"days\n" -"\n" -"\n" -"%(content)s\n" -" " -msgstr "" +#: perms/notifications.py:134 +msgid "Application permissions is about to expire" +msgstr "应用授权规则即将过期" -#: perms/notifications.py:169 perms/notifications.py:191 -#, fuzzy -#| msgid "Applications" -msgid "Applications may expire" -msgstr "应用管理" - -#: perms/notifications.py:172 -#, fuzzy, python-format -#| msgid "" -#| "\n" -#| " Hello %(name)s:\n" -#| "
\n" -#| " Your account will expire in %(date_expired)s,\n" -#| "
\n" -#| " In order not to affect your normal work, please contact the " -#| "administrator for confirmation.\n" -#| "
\n" -#| " " -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" Your permissions for the following applications may expire in " -"three days:\n" -"
\n" -" %(apps)s\n" -"
\n" -" Please contact the administrator\n" -" " -msgstr "" -"\n" -" 您好 %(name)s:\n" -"
\n" -" 您的账户会在 %(date_expired)s 过期,\n" -"
\n" -" 为了不影响您正常工作,请联系管理员确认。\n" -"
\n" -" " - -#: perms/notifications.py:194 -#, python-format -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"\n" -"Your permissions for the following applications may expire in three days:\n" -"\n" -"\n" -"%(apps)s\n" -"\n" -"\n" -"Please contact the administrator\n" -" " -msgstr "" - -#: perms/notifications.py:220 perms/notifications.py:241 -#: perms/notifications.py:268 perms/notifications.py:291 -#, fuzzy -#| msgid "Application permission" -msgid "Application permission will expired" -msgstr "应用管理" - -#: perms/notifications.py:223 -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" The following application permissions of organization %(org) " -"will expire in three days\n" -"
\n" -" %(perms)s\n" -" " -msgstr "" - -#: perms/notifications.py:244 -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"\n" -"The following application permissions of organization %(org) will expire in " -"three days\n" -"\n" -"\n" -"%(perms)s\n" -" " -msgstr "" - -#: perms/notifications.py:274 -#, fuzzy, python-format -#| msgid "" -#| "\n" -#| " Hello %(name)s:\n" -#| "
\n" -#| " Your account will expire in %(date_expired)s,\n" -#| "
\n" -#| " In order not to affect your normal work, please contact the " -#| "administrator for confirmation.\n" -#| "
\n" -#| " " -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" The following application permissions will expire in three days\n" -"
\n" -" %(content)s\n" -" " -msgstr "" -"\n" -" 您好 %(name)s:\n" -"
\n" -" 您的账户会在 %(date_expired)s 过期,\n" -"
\n" -" 为了不影响您正常工作,请联系管理员确认。\n" -"
\n" -" " - -#: perms/notifications.py:297 -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"\n" -"The following application permissions of organization %(org) will expire in " -"three days\n" -"\n" -"\n" -"%(content)s\n" -" " -msgstr "" +#: perms/notifications.py:137 +msgid "application permissions of organization {}" +msgstr "组织 ({}) 的应用授权" #: perms/serializers/application/permission.py:18 #: perms/serializers/application/permission.py:38 @@ -2836,11 +2630,27 @@ msgstr "节点名称" msgid "System users display" msgstr "系统用户名称" +#: perms/templates/perms/_msg_item_permissions_expire.html:7 +#: perms/templates/perms/_msg_permed_items_expire.html:7 +#, python-format +msgid "" +"\n" +" The following %(item_type)s will expire in 3 days\n" +" " +msgstr "" +"\n" +" 以下 %(item_type)s 即将在 3 天后过期\n" +" " + +#: perms/templates/perms/_msg_permed_items_expire.html:22 +msgid "If you have any question, please contact the administrator" +msgstr "如果有疑问或需求,请联系系统管理员" + #: settings/api/alibaba_sms.py:30 settings/api/tencent_sms.py:34 msgid "test_phone is required" msgstr "测试手机号 该字段是必填项。" -#: settings/api/alibaba_sms.py:51 settings/api/dingtalk.py:36 +#: settings/api/alibaba_sms.py:51 settings/api/dingtalk.py:31 #: settings/api/feishu.py:35 settings/api/tencent_sms.py:56 #: settings/api/wecom.py:36 msgid "Test success" @@ -3303,7 +3113,7 @@ msgstr "邮件的敬语" #: settings/serializers/email.py:55 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" -msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" +msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 你好)" #: settings/serializers/email.py:59 msgid "Create user email content" @@ -3501,12 +3311,12 @@ msgid "" msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭" #: settings/serializers/security.py:114 -msgid "Replay watermark" -msgstr "录像水印" +msgid "Enable watermark" +msgstr "开启水印" #: settings/serializers/security.py:115 -msgid "Enabled, the session replay contains watermark information" -msgstr "启用后,会话录像将包含水印信息" +msgid "Enabled, the web session and replay contains watermark information" +msgstr "启用后,Web 会话和录像将包含水印信息" #: settings/serializers/security.py:119 msgid "Connection max idle time" @@ -3928,7 +3738,7 @@ msgstr "数据库应用" msgid "Perms" msgstr "权限管理" -#: templates/_nav.html:97 terminal/notifications.py:20 +#: templates/_nav.html:97 terminal/notifications.py:22 msgid "Sessions" msgstr "会话管理" @@ -4038,11 +3848,11 @@ msgstr "确认删除" msgid "Are you sure delete" msgstr "您确定删除吗?" -#: templates/flash_message_standalone.html:28 +#: templates/flash_message_standalone.html:25 msgid "Cancel" msgstr "取消" -#: templates/flash_message_standalone.html:37 +#: templates/flash_message_standalone.html:34 msgid "Go" msgstr "立即" @@ -4271,6 +4081,7 @@ msgstr "输出" #: terminal/backends/command/models.py:23 terminal/models/sharing.py:15 #: terminal/models/sharing.py:58 +#: terminal/templates/terminal/_msg_command_alert.html:16 msgid "Session" msgstr "会话" @@ -4437,109 +4248,14 @@ msgstr "命令存储" msgid "Replay storage" msgstr "录像存储" -#: terminal/notifications.py:66 +#: terminal/notifications.py:68 msgid "Danger command alert" msgstr "危险命令告警" -#: terminal/notifications.py:82 -#, python-format -msgid "" -"\n" -"Command: %(command)s\n" -"Asset: %(hostname)s (%(host_ip)s)\n" -"User: %(user)s\n" -"Level: %(risk_level)s\n" -"Session: %(session_detail_url)s?oid=%(oid)s\n" -" " -msgstr "" -"\n" -"命令: %(command)s\n" -"资产: %(hostname)s (%(host_ip)s)\n" -"用户: %(user)s\n" -"等级: %(risk_level)s\n" -"会话: %(session_detail_url)s?oid=%(oid)s\n" -" " - -#: terminal/notifications.py:113 -#, python-format -msgid "" -"\n" -" Command: %(command)s\n" -"
\n" -" Asset: %(hostname)s (%(host_ip)s)\n" -"
\n" -" User: %(user)s\n" -"
\n" -" Level: %(risk_level)s\n" -"
\n" -" Session: session " -"detail\n" -"
\n" -" " -msgstr "" -"\n" -" 命令: %(command)s\n" -"
\n" -" 资产: %(hostname)s (%(host_ip)s)\n" -"
\n" -" 用户: %(user)s\n" -"
\n" -" 等级: %(risk_level)s\n" -"
\n" -" 会话: 会话详情\n" -"
\n" -" " - -#: terminal/notifications.py:142 +#: terminal/notifications.py:106 msgid "Batch danger command alert" msgstr "批量危险命令告警" -#: terminal/notifications.py:153 -#, python-format -msgid "" -"\n" -" Assets: %(assets)s\n" -"
\n" -" User: %(user)s\n" -"
\n" -" Level: %(risk_level)s\n" -"
\n" -"\n" -" ----------------- Commands ---------------- " -"
\n" -" %(command)s
\n" -" ----------------- Commands ---------------- " -"
\n" -" " -msgstr "" -"\n" -" 资产: %(assets)s\n" -"
\n" -" 用户: %(user)s\n" -"
\n" -" 等级: %(risk_level)s\n" -"
\n" -"
\n" -" ----------------- 命令 ----------------
\n" -" %(command)s
\n" -" ----------------- 命令 ----------------
\n" -"
\n" -" " - -#: terminal/notifications.py:180 -#, python-format -msgid "" -"\n" -"Assets: %(assets)s\n" -"User: %(user)s\n" -"Level: %(risk_level)s\n" -"\n" -"Commands 👇 ------------\n" -"%(command)s\n" -"------------------------\n" -" " -msgstr "" - #: terminal/serializers/session.py:31 msgid "User ID" msgstr "用户 ID" @@ -4641,6 +4357,15 @@ msgstr "忽略证书认证" msgid "Not found" msgstr "没有发现" +#: terminal/templates/terminal/_msg_command_alert.html:13 +#: terminal/templates/terminal/_msg_command_execue_alert.html:7 +msgid "Level" +msgstr "级别" + +#: terminal/templates/terminal/_msg_command_alert.html:16 +msgid "view" +msgstr "查看" + #: tickets/const.py:8 msgid "General" msgstr "一般" @@ -4893,15 +4618,19 @@ msgstr "流程" msgid "TicketFlow" msgstr "工单流程" -#: tickets/notifications.py:57 -msgid "click here to review" -msgstr "点击查看" +#: tickets/notifications.py:46 +msgid "New Ticket - {} ({})" +msgstr "新工单 - {} ({})" -#: tickets/notifications.py:75 +#: tickets/notifications.py:54 msgid "Your has a new ticket, applicant - {}" msgstr "你有一个新的工单, 申请人 - {}" -#: tickets/notifications.py:88 +#: tickets/notifications.py:68 +msgid "Ticket has processed - {} ({})" +msgstr "你的工单已被处理, 处理人 - {} ({})" + +#: tickets/notifications.py:77 msgid "Your ticket has been processed, processor - {}" msgstr "你的工单已被处理, 处理人 - {}" @@ -5021,6 +4750,12 @@ msgstr "请选择受理人" msgid "The current organization type already exists" msgstr "当前组织已存在该类型" +#: tickets/templates/tickets/_msg_ticket.html:11 +#, fuzzy +#| msgid "click here to review" +msgid "Click here to review" +msgstr "点击查看" + #: users/api/user.py:208 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置" @@ -5177,462 +4912,33 @@ msgstr "管理员" msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/notifications.py:19 users/notifications.py:52 +#: users/notifications.py:20 +#: users/templates/users/_msg_password_expire_reminder.html:17 #: users/templates/users/reset_password.html:5 #: users/templates/users/reset_password.html:6 msgid "Reset password" msgstr "重置密码" -#: users/notifications.py:20 -#, python-format -msgid "" -"\n" -"Hello %(name)s:\n" -"Please click the link below to reset your password, if not your request, " -"concern your account security\n" -"\n" -"Click here reset password 👇\n" -"%(rest_password_url)s?token=%(rest_password_token)s\n" -"\n" -"This link is valid for 1 hour. After it expires, \n" -"\n" -"request new one 👇\n" -"%(forget_password_url)s?email=%(email)s\n" -"\n" -"-------------------\n" -"\n" -"Login direct 👇\n" -"%(login_url)s\n" -"\n" -msgstr "" -"\n" -"您好 %(name)s:\n" -"请点击下面链接重置密码, 如果不是您申请的,请关注账号安全\n" -" \n" -"请点击这里设置密码 👇\n" -"%(rest_password_url)s?token=%(rest_password_token)s\n" -"\n" -"这个链接有效期1小时, 超过时间您可以, \n" -"\n" -"重新申请 👇\n" -"%(forget_password_url)s?email=%(email)s\n" -"\n" -"-------------------\n" -"\n" -"直接登录 👇\n" -"%(login_url)s\n" -"\n" - -#: users/notifications.py:53 -#, python-format -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" Please click the link below to reset your password, if not your " -"request, concern your account security\n" -"
\n" -" Click here reset password\n" -"
\n" -" This link is valid for 1 hour. After it expires, request new one\n" -" \n" -"
\n" -" ---\n" -" \n" -"
\n" -" Login direct\n" -" \n" -"
\n" -" " -msgstr "" -"\n" -" 您好 %(name)s:\n" -"
\n" -" 请点击下面链接重置密码, 如果不是您申请的,请关注账号安全\n" -"
\n" -" 请点击这" -"里设置密码 \n" -"
\n" -" 这个链接有效期1小时, 超过时间您可以重新申请\n" -"\n" -"
\n" -" ---\n" -"\n" -"
\n" -" 直接登录\n" -"\n" -"
\n" -" " - -#: users/notifications.py:92 users/notifications.py:124 -#: users/views/profile/reset.py:127 +#: users/notifications.py:50 users/views/profile/reset.py:127 msgid "Reset password success" msgstr "重置密码成功" -#: users/notifications.py:93 -#, python-format -msgid "" -"\n" -" \n" -"Hi %(name)s:\n" -"\n" -"Your JumpServer password has just been successfully updated.\n" -"\n" -"If the password update was not initiated by you, your account may have " -"security issues. \n" -"It is recommended that you log on to the JumpServer immediately and change " -"your password.\n" -"\n" -"If you have any questions, you can contact the administrator.\n" -"\n" -"-------------------\n" -"\n" -"\n" -"IP Address: %(ip_address)s\n" -"\n" -"\n" -"Browser: %(browser)s\n" -" \n" -" " -msgstr "" -"\n" -" \n" -"Hi %(name)s:\n" -"\n" -"你的 JumpServer 密码刚刚已经成功更新。\n" -"\n" -"如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题。 \n" -"建议你立刻登录 JumpServer 更改密码。 \n" -"\n" -"如果你有任何疑问,可以联系管理员。\n" -"\n" -"-------------------\n" -"\n" -"\n" -"IP 地址: %(ip_address)s\n" -"\n" -"浏览器: %(browser)s\n" -" \n" -" " +#: users/notifications.py:78 +msgid "Password is about expire" +msgstr "密码即将过期" -#: users/notifications.py:125 -#, python-format -msgid "" -"\n" -" \n" -" Hi %(name)s:\n" -"
\n" -" \n" -" \n" -"
\n" -" Your JumpServer password has just been successfully updated.\n" -"
\n" -" \n" -"
\n" -" If the password update was not initiated by you, your account may " -"have security issues. \n" -" It is recommended that you log on to the JumpServer immediately and " -"change your password.\n" -"
\n" -"\n" -"
\n" -" If you have any questions, you can contact the administrator.\n" -"
\n" -"
\n" -" ---\n" -"
\n" -"
\n" -" IP Address: %(ip_address)s\n" -"
\n" -"
\n" -" Browser: %(browser)s\n" -"
\n" -" \n" -" " -msgstr "" -"\n" -" \n" -" Hi %(name)s:\n" -"
\n" -" \n" -" \n" -"
\n" -" 你的 JumpServer 密码刚刚已经成功更新。
\n" -" \n" -"
\n" -" 如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题。\n" -" 建议你立刻登录 JumpServer 更改密码。\n" -"
\n" -" \n" -"
\n" -" 如果你有任何疑问,可以联系管理员。
\n" -"
\n" -" ---\n" -"
\n" -"
\n" -" IP 地址: %(ip_address)s\n" -"
\n" -"
\n" -" 浏览器: %(browser)s\n" -"
\n" -" \n" -" " +#: users/notifications.py:105 +msgid "Account is about expire" +msgstr "账号即将过期" -#: users/notifications.py:170 users/notifications.py:206 -msgid "Security notice" -msgstr "安全通知" +#: users/notifications.py:127 +msgid "Reset SSH Key" +msgstr "重置 SSH 密钥" -#: users/notifications.py:171 -#, python-format -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"Your password will expire in %(date_password_expired)s,\n" -"\n" -"For your account security, please click on the link below to update your " -"password in time\n" -"\n" -"Click here update password 👇\n" -"%(update_password_url)s\n" -"\n" -"If your password has expired, please click 👇 to apply for a password reset " -"email.\n" -"%(forget_password_url)s?email=%(email)s\n" -"\n" -"-------------------\n" -"\n" -"Login direct 👇\n" -"%(login_url)s\n" -"\n" -" " -msgstr "" -"\n" -"您好 %(name)s:\n" -"\n" -"您的密码会在 %(date_password_expired)s 过期,\n" -"\n" -"为了您的账号安全,请点击下面的链接及时更新密码 \n" -"\n" -"请点击这里更新密码 👇\n" -"%(update_password_url)s\n" -"如果您的密码已经过期,请点击 👇 申请一份重置密码邮件。 \n" -"%(forget_password_url)s?email=%(email)s\n" -"\n" -"-------------------\n" -"\n" -"直接登录 👇\n" -"%(login_url)s\n" -"\n" -" " - -#: users/notifications.py:207 -#, python-format -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" Your password will expire in %(date_password_expired)s,\n" -"
\n" -" For your account security, please click on the link below to update " -"your password in time\n" -"
\n" -" Click here update password\n" -"
\n" -" If your password has expired, please click \n" -" Password " -"expired \n" -" to apply for a password reset email.\n" -" \n" -"
\n" -" ---\n" -" \n" -"
\n" -" Login direct\n" -" \n" -"
\n" -" " -msgstr "" -"\n" -" 您好 %(name)s:\n" -"
\n" -" 您的密码会在 %(date_password_expired)s 过期,\n" -"
\n" -" 为了您的账号安全,请点击下面的链接及时更新密码\n" -"
\n" -" 请点击这里更新密码\n" -"
\n" -" 如果您的密码已经过期,请点击 \n" -" 密码过期 \n" -" 申请一份重置密码邮件。\n" -"\n" -"
\n" -" ---\n" -"\n" -"
\n" -" 直接登录\n" -"\n" -"
\n" -" " - -#: users/notifications.py:244 users/notifications.py:263 -msgid "Expiration notice" -msgstr "过期通知" - -#: users/notifications.py:245 -#, python-format -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"Your account will expire in %(date_expired)s,\n" -"\n" -"In order not to affect your normal work, please contact the administrator " -"for confirmation.\n" -"\n" -" " -msgstr "" -"\n" -"您好 %(name)s:\n" -"\n" -"您的账户会在 %(date_expired)s 过期,\n" -"\n" -"为了不影响您正常工作,请联系管理员确认。\n" -" " - -#: users/notifications.py:264 -#, python-format -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" Your account will expire in %(date_expired)s,\n" -"
\n" -" In order not to affect your normal work, please contact the " -"administrator for confirmation.\n" -"
\n" -" " -msgstr "" -"\n" -" 您好 %(name)s:\n" -"
\n" -" 您的账户会在 %(date_expired)s 过期,\n" -"
\n" -" 为了不影响您正常工作,请联系管理员确认。\n" -"
\n" -" " - -#: users/notifications.py:284 users/notifications.py:305 -msgid "SSH Key Reset" -msgstr "重置SSH密钥" - -#: users/notifications.py:285 -#, python-format -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"Your ssh public key has been reset by site administrator.\n" -"Please login and reset your ssh public key.\n" -"\n" -"Login direct 👇\n" -"%(login_url)s\n" -"\n" -" " -msgstr "" -"\n" -"你好 %(name)s:\n" -"\n" -"您的密钥已被管理员重置,\n" -"请登录并重新设置您的密钥.\n" -"\n" -"直接登录 👇\n" -"%(login_url)s\n" -"\n" -" " - -#: users/notifications.py:306 -#, python-format -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" Your ssh public key has been reset by site administrator.\n" -" Please login and reset your ssh public key.\n" -"
\n" -" Login direct\n" -" \n" -"
\n" -" " -msgstr "" -"\n" -" 你好 %(name)s:\n" -"
\n" -" 您的密钥已被管理员重置,\n" -" 请登录并重新设置您的密钥.\n" -"
\n" -" 直接登录\n" -"\n" -"
\n" -" " - -#: users/notifications.py:328 users/notifications.py:348 -msgid "MFA Reset" +#: users/notifications.py:147 +msgid "Reset MFA" msgstr "重置 MFA" -#: users/notifications.py:329 -#, python-format -msgid "" -"\n" -"Hello %(name)s:\n" -"\n" -"Your MFA has been reset by site administrator.\n" -"Please login and reset your MFA.\n" -"\n" -"Login direct 👇 \n" -"%(login_url)s\n" -"\n" -" " -msgstr "" -"\n" -"你好 %(name)s:\n" -"\n" -"您的 MFA 已被管理员重置,\n" -"请登录并重新设置您的 MFA.\n" -"\n" -"直接登录 👇 \n" -"%(login_url)s\n" -"\n" -" " - -#: users/notifications.py:349 -#, python-format -msgid "" -"\n" -" Hello %(name)s:\n" -"
\n" -" Your MFA has been reset by site administrator.\n" -" Please login and reset your MFA.\n" -"
\n" -" Login direct\n" -" \n" -"
\n" -" " -msgstr "" -"\n" -" 你好 %(name)s:\n" -"
\n" -" 您的 MFA 已被管理员重置,\n" -" 请登录并重新设置您的 MFA.\n" -"
\n" -" 直接登录\n" -"\n" -"
\n" -" " - #: users/serializers/profile.py:29 msgid "The old password is incorrect" msgstr "旧密码错误" @@ -5751,6 +5057,56 @@ msgstr "的密码完成绑定操作" msgid "Loading" msgstr "加载中" +#: users/templates/users/_msg_account_expire_reminder.html:7 +msgid "Your account will expire in" +msgstr "您的账号即将过期" + +#: users/templates/users/_msg_account_expire_reminder.html:8 +msgid "" +"In order not to affect your normal work, please contact the administrator " +"for confirmation." +msgstr "" +"为了不影响您正常工作,请联系管理员确认。\n" +" " + +#: users/templates/users/_msg_password_expire_reminder.html:7 +msgid "Your password will expire in" +msgstr "您的密码将过期" + +#: users/templates/users/_msg_password_expire_reminder.html:8 +msgid "" +"For your account security, please click on the link below to update your " +"password in time" +msgstr "为了您的账号安全,请点击下面的链接及时更新密码" + +#: users/templates/users/_msg_password_expire_reminder.html:11 +msgid "Click here update password" +msgstr "点击这里更新密码" + +#: users/templates/users/_msg_password_expire_reminder.html:16 +msgid "If your password has expired, please click" +msgstr "如果你的密码已过期,先点击" + +#: users/templates/users/_msg_password_expire_reminder.html:18 +msgid "to apply for a password reset email." +msgstr "申请重置" + +#: users/templates/users/_msg_reset_mfa.html:7 +msgid "Your MFA has been reset by site administrator." +msgstr "你的 MFA 已经被管理员重置。" + +#: users/templates/users/_msg_reset_mfa.html:8 +msgid "Please login and reset your MFA." +msgstr "请登录并重新设置你的 MFA" + +#: users/templates/users/_msg_reset_ssh_key.html:7 +msgid "Your ssh public key has been reset by site administrator." +msgstr "你的 SSH 密钥已经被管理员重置。" + +#: users/templates/users/_msg_reset_ssh_key.html:8 +msgid "Please login and reset your ssh public key." +msgstr "请登录并重新设置你的密钥" + #: users/templates/users/_select_user_modal.html:5 msgid "Please Select User" msgstr "选择用户" @@ -5982,7 +5338,7 @@ msgstr "创建账户成功" #: users/utils.py:61 #, python-format msgid "Hello %(name)s" -msgstr "您好 %(name)s" +msgstr "你好 %(name)s" #: users/views/profile/otp.py:122 users/views/profile/otp.py:161 #: users/views/profile/otp.py:181 @@ -6638,6 +5994,57 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "" +#~ "\n" +#~ "Time: {}" +#~ msgstr "" +#~ "\n" +#~ "时间:{}" + +#~ msgid "asset permission" +#~ msgstr "资产授权" + +#~ msgid "Asset permissions will expired" +#~ msgstr "资产授权即将过期" + +#, python-brace-format +#~ msgid "" +#~ "\n" +#~ "Organization: {org}\n" +#~ "Permissions: {perms}\n" +#~ msgstr "" +#~ "\n" +#~ "组织: {org}\n" +#~ "权限: {perms}\n" + +#~ msgid "asset permissions of organization" +#~ msgstr "组织资产授权" + +#~ msgid "application permissions of organization" +#~ msgstr "组织的应用授权 {}" + +#, python-brace-format +#~ msgid "" +#~ "\n" +#~ " Organization: {org} \n" +#~ " Permissions: {perms} \n" +#~ msgstr "" +#~ "\n" +#~ "组织: {org} \n" +#~ "授权: {perms} \n" + +#~ msgid "You've been hacked" +#~ msgstr "你被攻击了" + +#~ msgid "Binding DingTalk failed" +#~ msgstr "绑定钉钉失败" + +#~ msgid "Binding FeiShu failed" +#~ msgstr "绑定飞书失败" + +#~ msgid "Binding WeCom failed" +#~ msgstr "绑定企业微信失败" + #~ msgid "Enable Login MFA" #~ msgstr "启用登录MFA" diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py index f14a8ebe9..e888822d4 100644 --- a/apps/notifications/apps.py +++ b/apps/notifications/apps.py @@ -6,4 +6,5 @@ class NotificationsConfig(AppConfig): def ready(self): from . import signals_handler + from . import notifications super().ready() diff --git a/apps/notifications/notifications.py b/apps/notifications/notifications.py index d4456ac90..3bb8866c2 100644 --- a/apps/notifications/notifications.py +++ b/apps/notifications/notifications.py @@ -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 diff --git a/apps/notifications/site_msg.py b/apps/notifications/site_msg.py index 6e3f45f9d..7a3c9457f 100644 --- a/apps/notifications/site_msg.py +++ b/apps/notifications/site_msg.py @@ -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')) diff --git a/apps/ops/celery/utils.py b/apps/ops/celery/utils.py index c35db0f0e..3c38ee287 100644 --- a/apps/ops/celery/utils.py +++ b/apps/ops/celery/utils.py @@ -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() diff --git a/apps/ops/notifications.py b/apps/ops/notifications.py index 18cb9bf61..7e0643dcc 100644 --- a/apps/ops/notifications.py +++ b/apps/ops/notifications.py @@ -24,13 +24,6 @@ class ServerPerformanceMessage(SystemMessage): 'message': self._msg } - def get_text_msg(self) -> dict: - subject = self._msg[:80] - return { - 'subject': subject.replace('
', '; '), - 'message': self._msg.replace('
', '\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 = '
'.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})' ), }, } diff --git a/apps/perms/apps.py b/apps/perms/apps.py index 5bb7420bb..d6ce1f752 100644 --- a/apps/perms/apps.py +++ b/apps/perms/apps.py @@ -9,3 +9,4 @@ class PermsConfig(AppConfig): def ready(self): super().ready() from . import signals_handler + from . import notifications diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py index 8d93369ac..a8319b92f 100644 --- a/apps/perms/notifications.py +++ b/apps/perms/notifications.py @@ -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: -
- Your permissions for the following assets may expire in three days: -
- %(assets)s -
- 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: -
- The following asset permissions of organization %(org) will expire in three days -
- %(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'
Orgnization: {org}
Permissions: {",".join(str(perm) for perm in perms)}
' - - message = _(""" - Hello %(name)s: -
- The following asset permissions will expire in three days -
- %(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: -
- Your permissions for the following applications may expire in three days: -
- %(apps)s -
- 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: -
- The following application permissions of organization %(org) will expire in three days -
- %(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'
Orgnization: {org}
Permissions: {",".join(str(perm) for perm in perms)}
' - - message = _(""" - Hello %(name)s: -
- The following application permissions will expire in three days -
- %(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) diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index a5ab4992c..023913a41 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -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() diff --git a/apps/perms/templates/perms/_msg_item_permissions_expire.html b/apps/perms/templates/perms/_msg_item_permissions_expire.html new file mode 100644 index 000000000..f96b5e61d --- /dev/null +++ b/apps/perms/templates/perms/_msg_item_permissions_expire.html @@ -0,0 +1,16 @@ +{% load i18n %} +

+ {% trans 'Hello' %} {{ name }}, +

+ +

+ {% blocktranslate %} + The following {{ item_type }} will expire in 3 days + {% endblocktranslate %} +

+ +
    + {% for item, url in items_with_url %} +
  • {{ item }}
  • + {% endfor %} +
diff --git a/apps/perms/templates/perms/_msg_permed_items_expire.html b/apps/perms/templates/perms/_msg_permed_items_expire.html new file mode 100644 index 000000000..6a73f1b5f --- /dev/null +++ b/apps/perms/templates/perms/_msg_permed_items_expire.html @@ -0,0 +1,24 @@ +{% load i18n %} +

+ {% trans 'Hello' %} {{ name }}, +

+ +

+ {% blocktranslate %} + The following {{ item_type }} will expire in 3 days + {% endblocktranslate %} +

+ +
    + {% for item in items %} +
  • {{ item }}
  • + {% endfor %} +
+ +
+

+ ---
+ + {% trans 'If you have any question, please contact the administrator' %} + +

diff --git a/apps/settings/serializers/security.py b/apps/settings/serializers/security.py index 1225de0a4..06e5038fb 100644 --- a/apps/settings/serializers/security.py +++ b/apps/settings/serializers/security.py @@ -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, diff --git a/apps/terminal/notifications.py b/apps/terminal/notifications.py index c263440c3..461bc94a1 100644 --- a/apps/terminal/notifications.py +++ b/apps/terminal/notifications.py @@ -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 -
- Asset: %(hostname)s (%(host_ip)s) -
- User: %(user)s -
- Level: %(risk_level)s -
- Session: session detail -
- """) % { + 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', '
') - assets = ', '.join([str(asset) for asset in command['assets']]) - message = _(""" - Assets: %(assets)s -
- User: %(user)s -
- Level: %(risk_level)s -
+ 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 ----------------
- %(command)s
- ----------------- Commands ----------------
- """) % { + 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 diff --git a/apps/terminal/templates/terminal/_msg_command_alert.html b/apps/terminal/templates/terminal/_msg_command_alert.html new file mode 100644 index 000000000..b980e87bc --- /dev/null +++ b/apps/terminal/templates/terminal/_msg_command_alert.html @@ -0,0 +1,17 @@ +{% load i18n %} + +

+ {% trans 'Command' %}: {{ command }} +

+

+ {% trans 'Asset' %}: {{ hostname }}({{ host_ip }}) +

+

+ {% trans 'User' %}: {{ user }} +

+

+ {% trans 'Level' %}: {{ risk_level }} +

+

+ {% trans 'Session' %}: {% trans 'view' %} +

diff --git a/apps/terminal/templates/terminal/_msg_command_execute_alert.html b/apps/terminal/templates/terminal/_msg_command_execute_alert.html new file mode 100644 index 000000000..fcea490c6 --- /dev/null +++ b/apps/terminal/templates/terminal/_msg_command_execute_alert.html @@ -0,0 +1,24 @@ +{% load i18n %} + +

+ {% trans 'User' %}: {{ user }} +

+

+ {% trans 'Level' %}: {{ risk_level }} +

+
+ {% trans 'Command' %}:
+
+    {{ command }}
+    
+
+
+ {% trans 'Assets' %}:
+
    + {% for asset, url in assets_with_url %} +
  • + {{ asset }} +
  • + {% endfor %} +
+
diff --git a/apps/tickets/apps.py b/apps/tickets/apps.py index d155cbde2..a7beac9a4 100644 --- a/apps/tickets/apps.py +++ b/apps/tickets/apps.py @@ -6,4 +6,5 @@ class TicketsConfig(AppConfig): def ready(self): from . import signals_handler + from . import notifications return super().ready() diff --git a/apps/tickets/notifications.py b/apps/tickets/notifications.py index e0fba3d6c..22e3477c6 100644 --- a/apps/tickets/notifications.py +++ b/apps/tickets/notifications.py @@ -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 = ''' -
-

- {title} -

-
- {body} -
-
- - {ticket_detail_url_description} - -
-
-''' - 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('
', '').replace('
', '') + body=self.ticket.body.replace('\n', '
'), ) + 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', '
'), - ) - 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) diff --git a/apps/tickets/templates/tickets/_msg_ticket.html b/apps/tickets/templates/tickets/_msg_ticket.html new file mode 100644 index 000000000..8fc5afb81 --- /dev/null +++ b/apps/tickets/templates/tickets/_msg_ticket.html @@ -0,0 +1,14 @@ +{% load i18n %} +
+

+ {{ title}} +

+
+ {{ body}} +
+
+ + {% trans 'Click here to review' %} + +
+
\ No newline at end of file diff --git a/apps/users/apps.py b/apps/users/apps.py index f37fa6cf5..a40d54d84 100644 --- a/apps/users/apps.py +++ b/apps/users/apps.py @@ -8,4 +8,5 @@ class UsersConfig(AppConfig): def ready(self): from . import signals_handler + from . import notifications super().ready() diff --git a/apps/users/notifications.py b/apps/users/notifications.py index 57b2c9308..ab04b6fae 100644 --- a/apps/users/notifications.py +++ b/apps/users/notifications.py @@ -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: -
- Please click the link below to reset your password, if not your request, concern your account security -
- Click here reset password -
- This link is valid for 1 hour. After it expires, request new one - -
- --- - -
- Login direct - -
- """) % { - '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: -
- - -
- 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 -
-
- Browser: %(browser)s -
- - """) % { + 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: -
- 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 -
- If your password has expired, please click - Password expired - to apply for a password reset email. - -
- --- - -
- Login direct - -
- """) % { + 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: -
- 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'), + 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: -
- Your ssh public key has been reset by site administrator. - Please login and reset your ssh public key. -
- Login direct - -
- """) % { + 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: -
- Your MFA has been reset by site administrator. - Please login and reset your MFA. -
- Login direct - -
- """) % { + 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) diff --git a/apps/users/templates/users/_msg_account_expire_reminder.html b/apps/users/templates/users/_msg_account_expire_reminder.html new file mode 100644 index 000000000..c6799624f --- /dev/null +++ b/apps/users/templates/users/_msg_account_expire_reminder.html @@ -0,0 +1,9 @@ +{% load i18n %} + +

+ {% trans 'Hello' %} {{ name }}, +

+

+ {% trans 'Your account will expire in' %} {{ date_expired }},
+ {% trans 'In order not to affect your normal work, please contact the administrator for confirmation.' %} +

diff --git a/apps/users/templates/users/_msg_password_expire_reminder.html b/apps/users/templates/users/_msg_password_expire_reminder.html new file mode 100644 index 000000000..5037a332f --- /dev/null +++ b/apps/users/templates/users/_msg_password_expire_reminder.html @@ -0,0 +1,22 @@ +{% load i18n %} + +

+ {% trans 'Hello' %} {{ name }}, +

+

+ {% trans 'Your password will expire in' %} {{ date_password_expired }},
+ {% trans 'For your account security, please click on the link below to update your password in time' %} +
+
+ {% trans 'Click here update password' %} +
+

+ +

+ {% trans 'If your password has expired, please click' %} + {% trans 'Reset password' %} + {% trans 'to apply for a password reset email.' %} +

+--- +
+{% trans 'Login direct' %} \ No newline at end of file diff --git a/apps/users/templates/users/_msg_reset_mfa.html b/apps/users/templates/users/_msg_reset_mfa.html new file mode 100644 index 000000000..073d97ccb --- /dev/null +++ b/apps/users/templates/users/_msg_reset_mfa.html @@ -0,0 +1,12 @@ +{% load i18n %} + +

+ {% trans 'Hello' %} {{ name }}, +

+

+ {% trans 'Your MFA has been reset by site administrator.' %}
+ {% trans 'Please login and reset your MFA.' %} +

+

+ {% trans 'Login direct' %} +

\ No newline at end of file diff --git a/apps/users/templates/users/_msg_reset_ssh_key.html b/apps/users/templates/users/_msg_reset_ssh_key.html new file mode 100644 index 000000000..ab705aca7 --- /dev/null +++ b/apps/users/templates/users/_msg_reset_ssh_key.html @@ -0,0 +1,12 @@ +{% load i18n %} + +

+ {% trans 'Hello' %} {{ name }}, +

+

+ {% trans 'Your ssh public key has been reset by site administrator.' %}
+ {% trans 'Please login and reset your ssh public key.' %} +

+

+ {% trans 'Login direct' %} +

\ No newline at end of file diff --git a/apps/users/views/profile/otp.py b/apps/users/views/profile/otp.py index 7966dda8e..545ebf36d 100644 --- a/apps/users/views/profile/otp.py +++ b/apps/users/views/profile/otp.py @@ -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, diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a33745ae1..49d7c5a5e 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -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 \ No newline at end of file