mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-08-01 06:45:55 +00:00
Merge pull request #14943 from jumpserver/pr@dev@update_pam
merge: with dev
This commit is contained in:
commit
33f3281a1f
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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():
|
||||||
"""
|
"""
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 = ''
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
}
|
}
|
@ -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": "执行汇总"
|
||||||
}
|
}
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user