mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-07-04 10:36:37 +00:00
merge: with dev
This commit is contained in:
commit
0213154a19
5
apps/audits/const.py
Normal file
5
apps/audits/const.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
DEFAULT_CITY = _("Unknown")
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.db.models.signals import (
|
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.dispatch import receiver
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -14,25 +14,25 @@ from rest_framework.renderers import JSONRenderer
|
|||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
from assets.models import Asset, SystemUser
|
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 jumpserver.utils import current_request
|
||||||
from common.utils import get_request_ip, get_logger, get_syslogger
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from users.signals import post_user_change_password
|
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 terminal.models import Session, Command
|
||||||
from common.utils.encode import model_to_json
|
|
||||||
from .utils import write_login_log
|
from .utils import write_login_log
|
||||||
from . import models
|
from . import models
|
||||||
from .models import OperateLog
|
from .models import OperateLog
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from perms.models import AssetPermission, ApplicationPermission
|
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__)
|
logger = get_logger(__name__)
|
||||||
sys_logger = get_syslogger(__name__)
|
sys_logger = get_syslogger(__name__)
|
||||||
json_render = JSONRenderer()
|
json_render = JSONRenderer()
|
||||||
|
|
||||||
|
|
||||||
MODELS_NEED_RECORD = (
|
MODELS_NEED_RECORD = (
|
||||||
# users
|
# users
|
||||||
'User', 'UserGroup',
|
'User', 'UserGroup',
|
||||||
@ -165,7 +165,6 @@ M2M_NEED_RECORD = {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
M2M_ACTION = {
|
M2M_ACTION = {
|
||||||
POST_ADD: 'add',
|
POST_ADD: 'add',
|
||||||
POST_REMOVE: 'remove',
|
POST_REMOVE: 'remove',
|
||||||
@ -305,6 +304,7 @@ def generate_data(username, request, login_type=None):
|
|||||||
@receiver(post_auth_success)
|
@receiver(post_auth_success)
|
||||||
def on_user_auth_success(sender, user, request, login_type=None, **kwargs):
|
def on_user_auth_success(sender, user, request, login_type=None, **kwargs):
|
||||||
logger.debug('User login success: {}'.format(user.username))
|
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 = generate_data(user.username, request, login_type=login_type)
|
||||||
data.update({'mfa': int(user.mfa_enabled), 'status': True})
|
data.update({'mfa': int(user.mfa_enabled), 'status': True})
|
||||||
write_login_log(**data)
|
write_login_log(**data)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import csv
|
import csv
|
||||||
import codecs
|
import codecs
|
||||||
from django.http import HttpResponse
|
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
|
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):
|
def write_login_log(*args, **kwargs):
|
||||||
from audits.models import UserLoginLog
|
from audits.models import UserLoginLog
|
||||||
default_city = _("Unknown")
|
|
||||||
ip = kwargs.get('ip') or ''
|
ip = kwargs.get('ip') or ''
|
||||||
if not (ip and validate_ip(ip)):
|
if not (ip and validate_ip(ip)):
|
||||||
ip = ip[:15]
|
ip = ip[:15]
|
||||||
city = default_city
|
city = DEFAULT_CITY
|
||||||
else:
|
else:
|
||||||
city = get_ip_city(ip) or default_city
|
city = get_ip_city(ip) or DEFAULT_CITY
|
||||||
kwargs.update({'ip': ip, 'city': city})
|
kwargs.update({'ip': ip, 'city': city})
|
||||||
UserLoginLog.objects.create(**kwargs)
|
UserLoginLog.objects.create(**kwargs)
|
||||||
|
75
apps/authentication/notifications.py
Normal file
75
apps/authentication/notifications.py
Normal file
@ -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 = _(
|
||||||
|
""
|
||||||
|
"<h3>{subject}</h3>"
|
||||||
|
"<p>Dear {server_name} user, Hello!</p>"
|
||||||
|
"<p>Your account has remote login behavior, please pay attention.</p>"
|
||||||
|
"<p>User: {username}</p>"
|
||||||
|
"<p>Login time: {time}</p>"
|
||||||
|
"<p>Login location: {city} ({ip})</p>"
|
||||||
|
"<p>If you suspect that the login behavior is abnormal, please modify "
|
||||||
|
"<p>the account password in time.</p>"
|
||||||
|
"<br>"
|
||||||
|
"<p>Thank you for your attention to {server_name}!</p>")
|
||||||
|
|
||||||
|
|
||||||
|
class DifferentCityLoginMessage(UserMessage):
|
||||||
|
def __init__(self, user, ip, city):
|
||||||
|
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
|
||||||
|
}
|
@ -4,6 +4,12 @@ import base64
|
|||||||
from Cryptodome.PublicKey import RSA
|
from Cryptodome.PublicKey import RSA
|
||||||
from Cryptodome.Cipher import PKCS1_v1_5
|
from Cryptodome.Cipher import PKCS1_v1_5
|
||||||
from Cryptodome import Random
|
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
|
from common.utils import get_logger
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
@ -43,3 +49,16 @@ def rsa_decrypt(cipher_text, rsa_private_key=None):
|
|||||||
cipher_decoded = base64.b16decode(hex_fixed.upper())
|
cipher_decoded = base64.b16decode(hex_fixed.upper())
|
||||||
message = cipher.decrypt(cipher_decoded, b'error').decode()
|
message = cipher.decrypt(cipher_decoded, b'error').decode()
|
||||||
return message
|
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()
|
||||||
|
@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-09-29 14:42+0800\n"
|
"POT-Creation-Date: 2021-09-29 14:51+0800\n"
|
||||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||||
@ -552,7 +552,7 @@ msgstr "创建日期"
|
|||||||
msgid "AuthBook"
|
msgid "AuthBook"
|
||||||
msgstr "账号"
|
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"
|
msgid "Unknown"
|
||||||
msgstr "未知"
|
msgstr "未知"
|
||||||
|
|
||||||
@ -1668,6 +1668,47 @@ msgstr "{} 需要 {} 复核"
|
|||||||
msgid "Expired"
|
msgid "Expired"
|
||||||
msgstr "过期时间"
|
msgstr "过期时间"
|
||||||
|
|
||||||
|
#: authentication/notifications.py:11
|
||||||
|
#, python-brace-format
|
||||||
|
msgid ""
|
||||||
|
"<h3>{subject}</h3><p>Dear {server_name} user, Hello!</p><p>Your account has "
|
||||||
|
"remote login behavior, please pay attention.</p><p>User: {username}</"
|
||||||
|
"p><p>Login time: {time}</p><p>Login location: {city} ({ip})</p><p>If you "
|
||||||
|
"suspect that the login behavior is abnormal, please modify <p>the account "
|
||||||
|
"password in time.</p><br><p>Thank you for your attention to {server_name}!</"
|
||||||
|
"p>"
|
||||||
|
msgstr ""
|
||||||
|
"<h3>{subject}</h3><p>尊敬的{server_name}用户, 您好!</p><p>您的账"
|
||||||
|
"号存在异地登录行为,请关注。</p><p>用户: {username}</p><p>登录时间: {time}</"
|
||||||
|
"p><p>登录地点: {city} ({ip})</p><p>若怀疑此次登录行为异常,请及时修改账号密"
|
||||||
|
"码。</p><br><p>感谢您对{server_name}的关注!</p>"
|
||||||
|
|
||||||
|
#: 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
|
#: authentication/sms_verify_code.py:17
|
||||||
msgid "The verification code has expired. Please resend it"
|
msgid "The verification code has expired. Please resend it"
|
||||||
msgstr "验证码已过期,请重新发送"
|
msgstr "验证码已过期,请重新发送"
|
||||||
@ -2853,12 +2894,16 @@ msgid "Enable WeCom Auth"
|
|||||||
msgstr "启用企业微信认证"
|
msgstr "启用企业微信认证"
|
||||||
|
|
||||||
#: settings/serializers/basic.py:9
|
#: settings/serializers/basic.py:9
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Object"
|
||||||
msgid "Subject"
|
msgid "Subject"
|
||||||
msgstr "主题"
|
msgstr "对象"
|
||||||
|
|
||||||
#: settings/serializers/basic.py:13
|
#: settings/serializers/basic.py:13
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Target url"
|
||||||
msgid "More url"
|
msgid "More url"
|
||||||
msgstr "更多信息链接"
|
msgstr "目标URL"
|
||||||
|
|
||||||
#: settings/serializers/basic.py:28
|
#: settings/serializers/basic.py:28
|
||||||
msgid "Site url"
|
msgid "Site url"
|
||||||
@ -6126,8 +6171,6 @@ msgid "AP-Singapore"
|
|||||||
msgstr "亚太-新加坡"
|
msgstr "亚太-新加坡"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:48
|
#: xpack/plugins/cloud/providers/huaweicloud.py:48
|
||||||
#, fuzzy
|
|
||||||
#| msgid "CN-Hong Kong"
|
|
||||||
msgid "CN-Hong Kong"
|
msgid "CN-Hong Kong"
|
||||||
msgstr "中国-香港"
|
msgstr "中国-香港"
|
||||||
|
|
||||||
@ -6281,9 +6324,6 @@ msgstr "旗舰版"
|
|||||||
msgid "Community edition"
|
msgid "Community edition"
|
||||||
msgstr "社区版"
|
msgstr "社区版"
|
||||||
|
|
||||||
#~ msgid "Announcement enabled"
|
|
||||||
#~ msgstr "启用公告"
|
|
||||||
|
|
||||||
#~ msgid "OpenID"
|
#~ msgid "OpenID"
|
||||||
#~ msgstr "OpenID"
|
#~ msgstr "OpenID"
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ coreapi==2.3.3
|
|||||||
coreschema==0.0.4
|
coreschema==0.0.4
|
||||||
cryptography==3.3.2
|
cryptography==3.3.2
|
||||||
decorator==4.1.2
|
decorator==4.1.2
|
||||||
Django==3.1.12
|
Django==3.1.13
|
||||||
django-auth-ldap==2.2.0
|
django-auth-ldap==2.2.0
|
||||||
django-bootstrap3==14.2.0
|
django-bootstrap3==14.2.0
|
||||||
django-celery-beat==2.0
|
django-celery-beat==2.0
|
||||||
@ -31,7 +31,7 @@ docutils==0.14
|
|||||||
ecdsa==0.13.3
|
ecdsa==0.13.3
|
||||||
enum-compat==0.0.2
|
enum-compat==0.0.2
|
||||||
ephem==3.7.6.0
|
ephem==3.7.6.0
|
||||||
eventlet==0.24.1
|
eventlet==0.31.1
|
||||||
future==0.16.0
|
future==0.16.0
|
||||||
ForgeryPy3==0.3.1
|
ForgeryPy3==0.3.1
|
||||||
greenlet==0.4.14
|
greenlet==0.4.14
|
||||||
@ -49,7 +49,7 @@ olefile==0.44
|
|||||||
openapi-codec==1.3.2
|
openapi-codec==1.3.2
|
||||||
paramiko==2.7.2
|
paramiko==2.7.2
|
||||||
passlib==1.7.1
|
passlib==1.7.1
|
||||||
Pillow==8.2.0
|
Pillow==8.3.2
|
||||||
pyasn1==0.4.8
|
pyasn1==0.4.8
|
||||||
pycparser==2.19
|
pycparser==2.19
|
||||||
pycryptodome==3.10.1
|
pycryptodome==3.10.1
|
||||||
@ -59,7 +59,7 @@ PyNaCl==1.2.1
|
|||||||
python-dateutil==2.6.1
|
python-dateutil==2.6.1
|
||||||
#python-gssapi==0.6.4
|
#python-gssapi==0.6.4
|
||||||
pytz==2018.3
|
pytz==2018.3
|
||||||
PyYAML==5.2
|
PyYAML==5.4
|
||||||
redis==3.5.3
|
redis==3.5.3
|
||||||
requests==2.25.1
|
requests==2.25.1
|
||||||
jms-storage==0.0.39
|
jms-storage==0.0.39
|
||||||
|
Loading…
Reference in New Issue
Block a user