diff --git a/apps/audits/const.py b/apps/audits/const.py new file mode 100644 index 000000000..97f6cc6b1 --- /dev/null +++ b/apps/audits/const.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# +from django.utils.translation import ugettext_lazy as _ + +DEFAULT_CITY = _("Unknown") diff --git a/apps/audits/signals_handler.py b/apps/audits/signals_handler.py index c793481e8..4ba7e8408 100644 --- a/apps/audits/signals_handler.py +++ b/apps/audits/signals_handler.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.db.models.signals import ( - post_save, post_delete, m2m_changed, pre_delete + post_save, m2m_changed, pre_delete ) from django.dispatch import receiver from django.conf import settings @@ -14,25 +14,25 @@ from rest_framework.renderers import JSONRenderer from rest_framework.request import Request from assets.models import Asset, SystemUser -from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR +from authentication.signals import post_auth_failed, post_auth_success +from authentication.utils import check_different_city_login from jumpserver.utils import current_request -from common.utils import get_request_ip, get_logger, get_syslogger from users.models import User from users.signals import post_user_change_password -from authentication.signals import post_auth_failed, post_auth_success from terminal.models import Session, Command -from common.utils.encode import model_to_json from .utils import write_login_log from . import models from .models import OperateLog from orgs.utils import current_org from perms.models import AssetPermission, ApplicationPermission +from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR +from common.utils import get_request_ip, get_logger, get_syslogger +from common.utils.encode import model_to_json logger = get_logger(__name__) sys_logger = get_syslogger(__name__) json_render = JSONRenderer() - MODELS_NEED_RECORD = ( # users 'User', 'UserGroup', @@ -165,7 +165,6 @@ M2M_NEED_RECORD = { ), } - M2M_ACTION = { POST_ADD: 'add', POST_REMOVE: 'remove', @@ -305,6 +304,7 @@ def generate_data(username, request, login_type=None): @receiver(post_auth_success) def on_user_auth_success(sender, user, request, login_type=None, **kwargs): logger.debug('User login success: {}'.format(user.username)) + check_different_city_login(user, request) data = generate_data(user.username, request, login_type=login_type) data.update({'mfa': int(user.mfa_enabled), 'status': True}) write_login_log(**data) diff --git a/apps/audits/utils.py b/apps/audits/utils.py index 8710bfc0f..e569a7ebb 100644 --- a/apps/audits/utils.py +++ b/apps/audits/utils.py @@ -1,8 +1,8 @@ import csv import codecs from django.http import HttpResponse -from django.utils.translation import ugettext as _ +from .const import DEFAULT_CITY from common.utils import validate_ip, get_ip_city @@ -27,12 +27,12 @@ def write_content_to_excel(response, header=None, login_logs=None, fields=None): def write_login_log(*args, **kwargs): from audits.models import UserLoginLog - default_city = _("Unknown") + ip = kwargs.get('ip') or '' if not (ip and validate_ip(ip)): ip = ip[:15] - city = default_city + city = DEFAULT_CITY else: - city = get_ip_city(ip) or default_city + city = get_ip_city(ip) or DEFAULT_CITY kwargs.update({'ip': ip, 'city': city}) UserLoginLog.objects.create(**kwargs) diff --git a/apps/authentication/notifications.py b/apps/authentication/notifications.py new file mode 100644 index 000000000..cad3c0f2c --- /dev/null +++ b/apps/authentication/notifications.py @@ -0,0 +1,75 @@ +from django.utils import timezone +from django.utils.translation import ugettext as _ + +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): + self.ip = ip + 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(), + username=self.user.username, + ip=self.ip, + time=self.time, + city=self.city, + ) + return { + 'subject': self.subject, + 'message': message + } diff --git a/apps/authentication/utils.py b/apps/authentication/utils.py index 594e4e68a..8c3820eb8 100644 --- a/apps/authentication/utils.py +++ b/apps/authentication/utils.py @@ -4,6 +4,12 @@ import base64 from Cryptodome.PublicKey import RSA from Cryptodome.Cipher import PKCS1_v1_5 from Cryptodome import Random + +from .notifications import DifferentCityLoginMessage +from audits.models import UserLoginLog +from audits.const import DEFAULT_CITY +from common.utils import get_request_ip +from common.utils import validate_ip, get_ip_city from common.utils import get_logger logger = get_logger(__file__) @@ -43,3 +49,16 @@ def rsa_decrypt(cipher_text, rsa_private_key=None): cipher_decoded = base64.b16decode(hex_fixed.upper()) message = cipher.decrypt(cipher_decoded, b'error').decode() return message + + +def check_different_city_login(user, request): + ip = get_request_ip(request) or '0.0.0.0' + + if not (ip and validate_ip(ip)): + city = DEFAULT_CITY + else: + city = get_ip_city(ip) or DEFAULT_CITY + + last_user_login = UserLoginLog.objects.filter(username=user.username, status=True).first() + if last_user_login.city != city: + DifferentCityLoginMessage(user, ip, city).publish_async() diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index fe3cb332b..8c0f67776 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-09-23 11:11+0800\n" +"POT-Creation-Date: 2021-09-29 10:53+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -552,7 +552,7 @@ msgstr "创建日期" msgid "AuthBook" msgstr "账号" -#: assets/models/base.py:30 assets/tasks/const.py:51 audits/utils.py:30 +#: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 msgid "Unknown" msgstr "未知" @@ -1667,6 +1667,47 @@ 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 +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:17 msgid "The verification code has expired. Please resend it" msgstr "验证码已过期,请重新发送" @@ -2544,7 +2585,7 @@ msgstr "测试手机号 该字段是必填项。" msgid "Test success" msgstr "测试成功" -#: settings/api/email.py:21 +#: settings/api/email.py:22 msgid "Test mail sent to {}, please check" msgstr "邮件已经发送{}, 请检查" @@ -2818,7 +2859,7 @@ msgid "SMS provider" msgstr "短信服务商" #: settings/serializers/auth/sms.py:17 settings/serializers/auth/sms.py:35 -#: settings/serializers/auth/sms.py:43 settings/serializers/email.py:69 +#: settings/serializers/auth/sms.py:43 settings/serializers/email.py:63 msgid "Signature" msgstr "签名" @@ -2909,89 +2950,89 @@ msgstr "上传下载" msgid "Cloud sync record keep days" msgstr "云同步记录" -#: settings/serializers/email.py:24 +#: settings/serializers/email.py:18 msgid "SMTP host" msgstr "SMTP 主机" -#: settings/serializers/email.py:25 +#: settings/serializers/email.py:19 msgid "SMTP port" msgstr "SMTP 端口" -#: settings/serializers/email.py:26 +#: settings/serializers/email.py:20 msgid "SMTP account" msgstr "SMTP 账号" -#: settings/serializers/email.py:28 +#: settings/serializers/email.py:22 msgid "SMTP password" msgstr "SMTP 密码" -#: settings/serializers/email.py:29 +#: settings/serializers/email.py:23 msgid "Tips: Some provider use token except password" msgstr "提示:一些邮件提供商需要输入的是授权码" -#: settings/serializers/email.py:32 +#: settings/serializers/email.py:26 msgid "Send user" msgstr "发件人" -#: settings/serializers/email.py:33 +#: settings/serializers/email.py:27 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号" -#: settings/serializers/email.py:36 +#: settings/serializers/email.py:30 msgid "Test recipient" msgstr "测试收件人" -#: settings/serializers/email.py:37 +#: settings/serializers/email.py:31 msgid "Tips: Used only as a test mail recipient" msgstr "提示:仅用来作为测试邮件收件人" -#: settings/serializers/email.py:40 +#: settings/serializers/email.py:34 msgid "Use SSL" msgstr "使用 SSL" -#: settings/serializers/email.py:41 +#: settings/serializers/email.py:35 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用 SSL" -#: settings/serializers/email.py:44 +#: settings/serializers/email.py:38 msgid "Use TLS" msgstr "使用 TLS" -#: settings/serializers/email.py:45 +#: settings/serializers/email.py:39 msgid "If SMTP port is 587, may be select" msgstr "如果SMTP端口是587,通常需要启用 TLS" -#: settings/serializers/email.py:48 +#: settings/serializers/email.py:42 msgid "Subject prefix" msgstr "主题前缀" -#: settings/serializers/email.py:55 +#: settings/serializers/email.py:49 msgid "Create user email subject" msgstr "邮件主题" -#: settings/serializers/email.py:56 +#: settings/serializers/email.py:50 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" -#: settings/serializers/email.py:60 +#: settings/serializers/email.py:54 msgid "Create user honorific" msgstr "邮件的敬语" -#: settings/serializers/email.py:61 +#: settings/serializers/email.py:55 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" -#: settings/serializers/email.py:65 +#: settings/serializers/email.py:59 msgid "Create user email content" msgstr "邮件的内容" -#: settings/serializers/email.py:66 +#: settings/serializers/email.py:60 msgid "Tips:When creating a user, send the content of the email" msgstr "提示: 创建用户时,发送设置密码邮件的内容" -#: settings/serializers/email.py:70 +#: settings/serializers/email.py:64 msgid "Tips: Email signature (eg:jumpserver)" msgstr "邮件署名 (如:jumpserver)" @@ -3872,7 +3913,7 @@ msgstr "测试成功" msgid "Test failure: Account invalid" msgstr "测试失败: 账户无效" -#: terminal/api/terminal.py:39 +#: terminal/api/terminal.py:41 msgid "Have online sessions" msgstr "有在线会话" @@ -4265,7 +4306,7 @@ msgstr "文档类型" msgid "Ignore Certificate Verification" msgstr "忽略证书认证" -#: terminal/serializers/terminal.py:78 terminal/serializers/terminal.py:86 +#: terminal/serializers/terminal.py:80 terminal/serializers/terminal.py:88 msgid "Not found" msgstr "没有发现"