Merge pull request #14943 from jumpserver/pr@dev@update_pam

merge: with dev
This commit is contained in:
老广 2025-02-27 17:14:59 +08:00 committed by GitHub
commit 33f3281a1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 77 additions and 26 deletions

View File

@ -138,10 +138,12 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
account.secret = getattr(recorder, 'new_secret', account.secret) account.secret = getattr(recorder, 'new_secret', account.secret)
account.date_updated = timezone.now() account.date_updated = timezone.now()
account.date_change_secret = timezone.now()
account.change_secret_status = ChangeSecretRecordStatusChoice.success
with safe_db_connection(): with safe_db_connection():
recorder.save(update_fields=['status', 'date_finished']) recorder.save(update_fields=['status', 'date_finished'])
account.save(update_fields=['secret', 'date_updated']) account.save(update_fields=['secret', 'date_updated', 'date_change_secret', 'change_secret_status'])
self.summary['ok_accounts'] += 1 self.summary['ok_accounts'] += 1
self.result['ok_accounts'].append( self.result['ok_accounts'].append(

View File

@ -7,6 +7,7 @@ from django_filters import rest_framework as drf_filters
from assets.models import Node from assets.models import Node
from common.drf.filters import BaseFilterSet from common.drf.filters import BaseFilterSet
from common.utils.timezone import local_zero_hour, local_now from common.utils.timezone import local_zero_hour, local_now
from .const.automation import ChangeSecretRecordStatusChoice
from .models import Account, GatheredAccount, ChangeSecretRecord, PushSecretRecord, IntegrationApplication from .models import Account, GatheredAccount, ChangeSecretRecord, PushSecretRecord, IntegrationApplication
@ -104,11 +105,11 @@ class AccountFilterSet(BaseFilterSet):
if name == "latest_secret_change_failed": if name == "latest_secret_change_failed":
queryset = queryset.filter(date_change_secret__gt=date).exclude( queryset = queryset.filter(date_change_secret__gt=date).exclude(
change_secret_status="ok" change_secret_status=ChangeSecretRecordStatusChoice.success
) )
if kwargs: if kwargs:
queryset = queryset.filter(date_last_login__gte=date) queryset = queryset.filter(**kwargs)
return queryset return queryset
@staticmethod @staticmethod

View File

@ -1,4 +1,5 @@
from django.db import models from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
@ -167,6 +168,10 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
return escape(value) return escape(value)
def update_last_login_date(self):
self.date_last_login = timezone.now()
self.save(update_fields=['date_last_login'])
def replace_history_model_with_mixin(): def replace_history_model_with_mixin():
""" """

View File

@ -3,7 +3,7 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework import exceptions from rest_framework import exceptions
from rest_framework.generics import CreateAPIView, RetrieveAPIView from rest_framework.generics import CreateAPIView
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
@ -23,8 +23,6 @@ __all__ = [
] ]
# MFASelectAPi 原来的名字 # MFASelectAPi 原来的名字
class MFASendCodeApi(AuthMixin, CreateAPIView): class MFASendCodeApi(AuthMixin, CreateAPIView):
""" """

View File

@ -1,5 +1,5 @@
from .otp import MFAOtp, otp_failed_msg
from .sms import MFASms
from .radius import MFARadius
from .custom import MFACustom from .custom import MFACustom
from .face import MFAFace from .face import MFAFace
from .otp import MFAOtp, otp_failed_msg
from .radius import MFARadius
from .sms import MFASms

View File

@ -1,10 +1,12 @@
import abc import abc
from django.core.cache import cache
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class BaseMFA(abc.ABC): class BaseMFA(abc.ABC):
placeholder = _('Please input security code') placeholder = _('Please input security code')
skip_cache_check = False
def __init__(self, user): def __init__(self, user):
""" """
@ -14,6 +16,25 @@ class BaseMFA(abc.ABC):
self.user = user self.user = user
self.request = None self.request = None
def check_code(self, code):
if self.skip_cache_check:
return self._check_code(code)
cache_key = f'{self.name}_{self.user.username}'
cache_code = cache.get(cache_key)
if cache_code == code:
return False, _(
"The two-factor code you entered has either already been used or has expired. "
"Please request a new one."
)
ok, msg = self._check_code(code)
if not ok:
return False, msg
cache.set(cache_key, code, 60 * 5)
return True, msg
def is_authenticated(self): def is_authenticated(self):
return self.user and self.user.is_authenticated return self.user and self.user.is_authenticated
@ -38,7 +59,7 @@ class BaseMFA(abc.ABC):
pass pass
@abc.abstractmethod @abc.abstractmethod
def check_code(self, code) -> tuple: def _check_code(self, code) -> tuple:
return False, 'Error msg' return False, 'Error msg'
@abc.abstractmethod @abc.abstractmethod

View File

@ -26,7 +26,7 @@ class MFACustom(BaseMFA):
display_name = MFAType.Custom.name display_name = MFAType.Custom.name
placeholder = _("MFA custom verification code") placeholder = _("MFA custom verification code")
def check_code(self, code): def _check_code(self, code):
assert self.is_authenticated() assert self.is_authenticated()
ok = False ok = False
try: try:

View File

@ -10,9 +10,9 @@ class MFAFace(BaseMFA, AuthFaceMixin):
name = MFAType.Face.value name = MFAType.Face.value
display_name = MFAType.Face.name display_name = MFAType.Face.name
placeholder = 'Face Recognition' placeholder = 'Face Recognition'
skip_cache_check = True
def check_code(self, code): def _check_code(self, code):
assert self.is_authenticated() assert self.is_authenticated()
try: try:

View File

@ -1,10 +1,9 @@
from django.utils.translation import gettext_lazy as _
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _
from .base import BaseMFA from .base import BaseMFA
from ..const import MFAType from ..const import MFAType
otp_failed_msg = _("OTP code invalid, or server time error") otp_failed_msg = _("OTP code invalid, or server time error")
@ -13,7 +12,7 @@ class MFAOtp(BaseMFA):
display_name = MFAType.OTP.name display_name = MFAType.OTP.name
placeholder = _('OTP verification code') placeholder = _('OTP verification code')
def check_code(self, code): def _check_code(self, code):
from users.utils import check_otp_code from users.utils import check_otp_code
assert self.is_authenticated() assert self.is_authenticated()

View File

@ -13,7 +13,7 @@ class MFARadius(BaseMFA):
display_name = MFAType.Radius.name display_name = MFAType.Radius.name
placeholder = _("Radius verification code") placeholder = _("Radius verification code")
def check_code(self, code=None): def _check_code(self, code=None):
assert self.is_authenticated() assert self.is_authenticated()
backend = RadiusBackend() backend = RadiusBackend()
username = self.user.username username = self.user.username

View File

@ -24,7 +24,7 @@ class MFASms(BaseMFA):
phone, backend=self.name, user_info=user_info phone, backend=self.name, user_info=user_info
) )
def check_code(self, code): def _check_code(self, code):
assert self.is_authenticated() assert self.is_authenticated()
ok = False ok = False
msg = '' msg = ''

View File

@ -381,4 +381,3 @@ def bulk_update_decorator(instance_model, batch_size=50, update_fields=None, tim
instance_model.objects.bulk_update(cache, update_fields) instance_model.objects.bulk_update(cache, update_fields)
return bulk_handle(handle, batch_size, timeout) return bulk_handle(handle, batch_size, timeout)

View File

@ -182,7 +182,7 @@
"Authentication": "Authentication", "Authentication": "Authentication",
"AuthorizedKeysChanged": "Authorized keys changed", "AuthorizedKeysChanged": "Authorized keys changed",
"AutoPush": "Auto push", "AutoPush": "Auto push",
"Automations": "Automations", "Automation": "Automation",
"AverageTimeCost": "Average spend time", "AverageTimeCost": "Average spend time",
"AwaitingMyApproval": "Assigned", "AwaitingMyApproval": "Assigned",
"Azure": "Azure (China)", "Azure": "Azure (China)",
@ -673,6 +673,7 @@
"InterfaceSettings": "Appearance", "InterfaceSettings": "Appearance",
"Interval": "Interval", "Interval": "Interval",
"IntervalOfCreateUpdatePage": "Unit: hour", "IntervalOfCreateUpdatePage": "Unit: hour",
"Integration": "Integration",
"InvalidJson": "Invalid json", "InvalidJson": "Invalid json",
"InviteSuccess": "Invitation successful", "InviteSuccess": "Invitation successful",
"InviteUser": "Invite", "InviteUser": "Invite",
@ -1112,8 +1113,14 @@
"RunningPath": "Running path", "RunningPath": "Running path",
"RunningPathHelpText": "Enter the run path of the script, this setting only applies to shell scripts", "RunningPathHelpText": "Enter the run path of the script, this setting only applies to shell scripts",
"RunningTimes": "Last 5 run times", "RunningTimes": "Last 5 run times",
"RunningSummary": "Running summary",
"SCP": "Sangfor cloud platform", "SCP": "Sangfor cloud platform",
"SMS": "Message", "SMS": "Message",
"AccountPushTask": "Account push task | Account push tasks",
"DiscoverAccountTask": "Account discovery task | Account discovery tasks",
"ChangeSecretTask": "Change secret task | Change secret tasks",
"AccountBackupTask": "Account backup task | Account backup tasks",
"ExecutionHistory": "Execution history",
"SMSProvider": "SMS service provider", "SMSProvider": "SMS service provider",
"SMTP": "Server", "SMTP": "Server",
"SPECIAL_CHAR_REQUIRED": "Must contain special characters", "SPECIAL_CHAR_REQUIRED": "Must contain special characters",
@ -1310,6 +1317,7 @@
"TaskList": "Tasks", "TaskList": "Tasks",
"TaskMonitor": "Monitoring", "TaskMonitor": "Monitoring",
"TaskPath": "Task path", "TaskPath": "Task path",
"TaskSummary": "Task summary",
"TechnologyConsult": "Technical consultation", "TechnologyConsult": "Technical consultation",
"TempPasswordTip": "The temporary password is valid for 300 seconds and becomes invalid immediately after use", "TempPasswordTip": "The temporary password is valid for 300 seconds and becomes invalid immediately after use",
"TempToken": "Temporary tokens", "TempToken": "Temporary tokens",
@ -1503,5 +1511,6 @@
"disallowSelfUpdateFields": "Not allowed to modify the current fields yourself", "disallowSelfUpdateFields": "Not allowed to modify the current fields yourself",
"forceEnableMFAHelpText": "If force enable, user can not disable by themselves", "forceEnableMFAHelpText": "If force enable, user can not disable by themselves",
"removeWarningMsg": "Are you sure you want to remove", "removeWarningMsg": "Are you sure you want to remove",
"ExecutionSummary": "Execution summary",
"setVariable": "Set variable" "setVariable": "Set variable"
} }

View File

@ -174,7 +174,7 @@
"Authentication": "认证", "Authentication": "认证",
"AuthorizedKeysChanged": "密钥变更", "AuthorizedKeysChanged": "密钥变更",
"AutoPush": "自动推送", "AutoPush": "自动推送",
"Automations": "自动化", "Automation": "自动化",
"AverageTimeCost": "平均花费时间", "AverageTimeCost": "平均花费时间",
"AwaitingMyApproval": "待我审批", "AwaitingMyApproval": "待我审批",
"Azure": "Azure (中国)", "Azure": "Azure (中国)",
@ -647,6 +647,7 @@
"InterfaceSettings": "界面设置", "InterfaceSettings": "界面设置",
"Interval": "间隔", "Interval": "间隔",
"IntervalOfCreateUpdatePage": "单位:时", "IntervalOfCreateUpdatePage": "单位:时",
"Integration": "应用集成",
"InvalidJson": "不是合法 JSON", "InvalidJson": "不是合法 JSON",
"InviteSuccess": "邀请成功", "InviteSuccess": "邀请成功",
"InviteUser": "邀请用户", "InviteUser": "邀请用户",
@ -1075,6 +1076,12 @@
"RunningPath": "运行路径", "RunningPath": "运行路径",
"RunningPathHelpText": "填写脚本的运行路径,此设置仅 shell 脚本生效", "RunningPathHelpText": "填写脚本的运行路径,此设置仅 shell 脚本生效",
"RunningTimes": "最近5次运行时间", "RunningTimes": "最近5次运行时间",
"RunningSummary": "运行中",
"AccountPushTask": "账号推送任务",
"DiscoverAccountTask": "账号发现任务",
"ChangeSecretTask": "账号改密任务",
"AccountBackupTask": "账号备份任务",
"ExecutionHistory": "执行历史",
"SCP": "深信服云平台", "SCP": "深信服云平台",
"SMS": "短信", "SMS": "短信",
"SMSProvider": "短信服务商", "SMSProvider": "短信服务商",
@ -1269,6 +1276,7 @@
"TaskList": "任务列表", "TaskList": "任务列表",
"TaskMonitor": "任务监控", "TaskMonitor": "任务监控",
"TaskPath": "任务路径", "TaskPath": "任务路径",
"TaskSummary": "任务汇总",
"TechnologyConsult": "技术咨询", "TechnologyConsult": "技术咨询",
"TempPasswordTip": "临时密码有效期为 300 秒,使用后立刻失效", "TempPasswordTip": "临时密码有效期为 300 秒,使用后立刻失效",
"TempToken": "临时密码", "TempToken": "临时密码",
@ -1459,5 +1467,8 @@
"disallowSelfUpdateFields": "不允许自己修改当前字段", "disallowSelfUpdateFields": "不允许自己修改当前字段",
"forceEnableMFAHelpText": "如果强制启用,用户无法自行禁用", "forceEnableMFAHelpText": "如果强制启用,用户无法自行禁用",
"removeWarningMsg": "你确定要移除", "removeWarningMsg": "你确定要移除",
"setVariable": "设置参数" "setVariable": "设置参数",
"TableSetting": "表格偏好",
"AccountDiscoverTask": "账号发现",
"ExecutionSummary": "执行汇总"
} }

View File

@ -11,6 +11,7 @@ from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from accounts.models import Account
from assets.models import Asset from assets.models import Asset
from common.const.signals import OP_LOG_SKIP_SIGNAL from common.const.signals import OP_LOG_SKIP_SIGNAL
from common.utils import get_object_or_none, lazyproperty from common.utils import get_object_or_none, lazyproperty
@ -125,6 +126,10 @@ class Session(OrgModelMixin):
def user_obj(self): def user_obj(self):
return User.objects.get(id=self.user_id) return User.objects.get(id=self.user_id)
@property
def account_obj(self):
return get_object_or_none(Account, pk=self.account_id)
def can_replay(self): def can_replay(self):
return self.has_replay return self.has_replay

View File

@ -5,9 +5,11 @@ from terminal.models import Session
@receiver(pre_save, sender=Session) @receiver(pre_save, sender=Session)
def on_session_pre_save(sender, instance,**kwargs): def on_session_pre_save(sender, instance, **kwargs):
if instance.need_update_cmd_amount: if instance.need_update_cmd_amount:
instance.cmd_amount = instance.compute_command_amount() instance.cmd_amount = instance.compute_command_amount()
if instance.account_obj:
instance.account_obj.update_last_login_date()
@receiver(post_save, sender=Session) @receiver(post_save, sender=Session)
@ -16,4 +18,3 @@ def on_session_finished(sender, instance: Session, created, **kwargs):
return return
# 清理一次可能因 task 未执行的缓存数据 # 清理一次可能因 task 未执行的缓存数据
Session.unlock_session(instance.id) Session.unlock_session(instance.id)

View File

@ -7,8 +7,8 @@ from django.http.response import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.templatetags.static import static from django.templatetags.static import static
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _
from django.utils._os import safe_join from django.utils._os import safe_join
from django.utils.translation import gettext as _
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView