diff --git a/apps/accounts/automations/base/manager.py b/apps/accounts/automations/base/manager.py index b52dd4c2a..bc33f588e 100644 --- a/apps/accounts/automations/base/manager.py +++ b/apps/accounts/automations/base/manager.py @@ -138,10 +138,12 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager): account.secret = getattr(recorder, 'new_secret', account.secret) account.date_updated = timezone.now() + account.date_change_secret = timezone.now() + account.change_secret_status = ChangeSecretRecordStatusChoice.success with safe_db_connection(): 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.result['ok_accounts'].append( diff --git a/apps/accounts/filters.py b/apps/accounts/filters.py index 32968ca71..b305196df 100644 --- a/apps/accounts/filters.py +++ b/apps/accounts/filters.py @@ -7,6 +7,7 @@ from django_filters import rest_framework as drf_filters from assets.models import Node from common.drf.filters import BaseFilterSet from common.utils.timezone import local_zero_hour, local_now +from .const.automation import ChangeSecretRecordStatusChoice from .models import Account, GatheredAccount, ChangeSecretRecord, PushSecretRecord, IntegrationApplication @@ -104,11 +105,11 @@ class AccountFilterSet(BaseFilterSet): if name == "latest_secret_change_failed": queryset = queryset.filter(date_change_secret__gt=date).exclude( - change_secret_status="ok" + change_secret_status=ChangeSecretRecordStatusChoice.success ) if kwargs: - queryset = queryset.filter(date_last_login__gte=date) + queryset = queryset.filter(**kwargs) return queryset @staticmethod diff --git a/apps/accounts/models/account.py b/apps/accounts/models/account.py index 1f363b678..5e90aee98 100644 --- a/apps/accounts/models/account.py +++ b/apps/accounts/models/account.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords @@ -167,6 +168,10 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount): 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(): """ diff --git a/apps/authentication/api/mfa.py b/apps/authentication/api/mfa.py index 4a22ad6a1..9ce1a07be 100644 --- a/apps/authentication/api/mfa.py +++ b/apps/authentication/api/mfa.py @@ -3,7 +3,7 @@ from django.shortcuts import get_object_or_404 from django.utils.translation import gettext as _ 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.response import Response from rest_framework.serializers import ValidationError @@ -23,8 +23,6 @@ __all__ = [ ] - - # MFASelectAPi 原来的名字 class MFASendCodeApi(AuthMixin, CreateAPIView): """ diff --git a/apps/authentication/mfa/__init__.py b/apps/authentication/mfa/__init__.py index 29d9e2937..3aa3b34a1 100644 --- a/apps/authentication/mfa/__init__.py +++ b/apps/authentication/mfa/__init__.py @@ -1,5 +1,5 @@ -from .otp import MFAOtp, otp_failed_msg -from .sms import MFASms -from .radius import MFARadius from .custom import MFACustom -from .face import MFAFace \ No newline at end of file +from .face import MFAFace +from .otp import MFAOtp, otp_failed_msg +from .radius import MFARadius +from .sms import MFASms diff --git a/apps/authentication/mfa/base.py b/apps/authentication/mfa/base.py index 8575997d6..ace5a1424 100644 --- a/apps/authentication/mfa/base.py +++ b/apps/authentication/mfa/base.py @@ -1,10 +1,12 @@ import abc +from django.core.cache import cache from django.utils.translation import gettext_lazy as _ class BaseMFA(abc.ABC): placeholder = _('Please input security code') + skip_cache_check = False def __init__(self, user): """ @@ -14,6 +16,25 @@ class BaseMFA(abc.ABC): self.user = user 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): return self.user and self.user.is_authenticated @@ -38,7 +59,7 @@ class BaseMFA(abc.ABC): pass @abc.abstractmethod - def check_code(self, code) -> tuple: + def _check_code(self, code) -> tuple: return False, 'Error msg' @abc.abstractmethod diff --git a/apps/authentication/mfa/custom.py b/apps/authentication/mfa/custom.py index 455b852f3..3b207ea3c 100644 --- a/apps/authentication/mfa/custom.py +++ b/apps/authentication/mfa/custom.py @@ -26,7 +26,7 @@ class MFACustom(BaseMFA): display_name = MFAType.Custom.name placeholder = _("MFA custom verification code") - def check_code(self, code): + def _check_code(self, code): assert self.is_authenticated() ok = False try: diff --git a/apps/authentication/mfa/face.py b/apps/authentication/mfa/face.py index 4edf46655..085fa38cf 100644 --- a/apps/authentication/mfa/face.py +++ b/apps/authentication/mfa/face.py @@ -10,9 +10,9 @@ class MFAFace(BaseMFA, AuthFaceMixin): name = MFAType.Face.value display_name = MFAType.Face.name placeholder = 'Face Recognition' + skip_cache_check = True - def check_code(self, code): - + def _check_code(self, code): assert self.is_authenticated() try: diff --git a/apps/authentication/mfa/otp.py b/apps/authentication/mfa/otp.py index fc7decee1..6e56f8a14 100644 --- a/apps/authentication/mfa/otp.py +++ b/apps/authentication/mfa/otp.py @@ -1,10 +1,9 @@ -from django.utils.translation import gettext_lazy as _ from django.shortcuts import reverse +from django.utils.translation import gettext_lazy as _ from .base import BaseMFA from ..const import MFAType - otp_failed_msg = _("OTP code invalid, or server time error") @@ -13,7 +12,7 @@ class MFAOtp(BaseMFA): display_name = MFAType.OTP.name placeholder = _('OTP verification code') - def check_code(self, code): + def _check_code(self, code): from users.utils import check_otp_code assert self.is_authenticated() diff --git a/apps/authentication/mfa/radius.py b/apps/authentication/mfa/radius.py index 8b24897bd..ff4bb9e04 100644 --- a/apps/authentication/mfa/radius.py +++ b/apps/authentication/mfa/radius.py @@ -13,7 +13,7 @@ class MFARadius(BaseMFA): display_name = MFAType.Radius.name placeholder = _("Radius verification code") - def check_code(self, code=None): + def _check_code(self, code=None): assert self.is_authenticated() backend = RadiusBackend() username = self.user.username diff --git a/apps/authentication/mfa/sms.py b/apps/authentication/mfa/sms.py index 218931584..72a9bc02c 100644 --- a/apps/authentication/mfa/sms.py +++ b/apps/authentication/mfa/sms.py @@ -24,7 +24,7 @@ class MFASms(BaseMFA): phone, backend=self.name, user_info=user_info ) - def check_code(self, code): + def _check_code(self, code): assert self.is_authenticated() ok = False msg = '' diff --git a/apps/common/decorators.py b/apps/common/decorators.py index 2f213e9a8..6e9cc0926 100644 --- a/apps/common/decorators.py +++ b/apps/common/decorators.py @@ -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) return bulk_handle(handle, batch_size, timeout) - diff --git a/apps/i18n/lina/en.json b/apps/i18n/lina/en.json index 23b026137..20818e50a 100644 --- a/apps/i18n/lina/en.json +++ b/apps/i18n/lina/en.json @@ -182,7 +182,7 @@ "Authentication": "Authentication", "AuthorizedKeysChanged": "Authorized keys changed", "AutoPush": "Auto push", - "Automations": "Automations", + "Automation": "Automation", "AverageTimeCost": "Average spend time", "AwaitingMyApproval": "Assigned", "Azure": "Azure (China)", @@ -673,6 +673,7 @@ "InterfaceSettings": "Appearance", "Interval": "Interval", "IntervalOfCreateUpdatePage": "Unit: hour", + "Integration": "Integration", "InvalidJson": "Invalid json", "InviteSuccess": "Invitation successful", "InviteUser": "Invite", @@ -1112,8 +1113,14 @@ "RunningPath": "Running path", "RunningPathHelpText": "Enter the run path of the script, this setting only applies to shell scripts", "RunningTimes": "Last 5 run times", + "RunningSummary": "Running summary", "SCP": "Sangfor cloud platform", "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", "SMTP": "Server", "SPECIAL_CHAR_REQUIRED": "Must contain special characters", @@ -1310,6 +1317,7 @@ "TaskList": "Tasks", "TaskMonitor": "Monitoring", "TaskPath": "Task path", + "TaskSummary": "Task summary", "TechnologyConsult": "Technical consultation", "TempPasswordTip": "The temporary password is valid for 300 seconds and becomes invalid immediately after use", "TempToken": "Temporary tokens", @@ -1503,5 +1511,6 @@ "disallowSelfUpdateFields": "Not allowed to modify the current fields yourself", "forceEnableMFAHelpText": "If force enable, user can not disable by themselves", "removeWarningMsg": "Are you sure you want to remove", + "ExecutionSummary": "Execution summary", "setVariable": "Set variable" } \ No newline at end of file diff --git a/apps/i18n/lina/zh.json b/apps/i18n/lina/zh.json index 0020678e6..db90ac7d4 100644 --- a/apps/i18n/lina/zh.json +++ b/apps/i18n/lina/zh.json @@ -174,7 +174,7 @@ "Authentication": "认证", "AuthorizedKeysChanged": "密钥变更", "AutoPush": "自动推送", - "Automations": "自动化", + "Automation": "自动化", "AverageTimeCost": "平均花费时间", "AwaitingMyApproval": "待我审批", "Azure": "Azure (中国)", @@ -647,6 +647,7 @@ "InterfaceSettings": "界面设置", "Interval": "间隔", "IntervalOfCreateUpdatePage": "单位:时", + "Integration": "应用集成", "InvalidJson": "不是合法 JSON", "InviteSuccess": "邀请成功", "InviteUser": "邀请用户", @@ -1075,6 +1076,12 @@ "RunningPath": "运行路径", "RunningPathHelpText": "填写脚本的运行路径,此设置仅 shell 脚本生效", "RunningTimes": "最近5次运行时间", + "RunningSummary": "运行中", + "AccountPushTask": "账号推送任务", + "DiscoverAccountTask": "账号发现任务", + "ChangeSecretTask": "账号改密任务", + "AccountBackupTask": "账号备份任务", + "ExecutionHistory": "执行历史", "SCP": "深信服云平台", "SMS": "短信", "SMSProvider": "短信服务商", @@ -1269,6 +1276,7 @@ "TaskList": "任务列表", "TaskMonitor": "任务监控", "TaskPath": "任务路径", + "TaskSummary": "任务汇总", "TechnologyConsult": "技术咨询", "TempPasswordTip": "临时密码有效期为 300 秒,使用后立刻失效", "TempToken": "临时密码", @@ -1459,5 +1467,8 @@ "disallowSelfUpdateFields": "不允许自己修改当前字段", "forceEnableMFAHelpText": "如果强制启用,用户无法自行禁用", "removeWarningMsg": "你确定要移除", - "setVariable": "设置参数" + "setVariable": "设置参数", + "TableSetting": "表格偏好", + "AccountDiscoverTask": "账号发现", + "ExecutionSummary": "执行汇总" } \ No newline at end of file diff --git a/apps/terminal/models/session/session.py b/apps/terminal/models/session/session.py index 85a8e8273..b037e83bc 100644 --- a/apps/terminal/models/session/session.py +++ b/apps/terminal/models/session/session.py @@ -11,6 +11,7 @@ from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from accounts.models import Account from assets.models import Asset from common.const.signals import OP_LOG_SKIP_SIGNAL from common.utils import get_object_or_none, lazyproperty @@ -125,6 +126,10 @@ class Session(OrgModelMixin): def user_obj(self): 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): return self.has_replay diff --git a/apps/terminal/signal_handlers/session.py b/apps/terminal/signal_handlers/session.py index 4c79de9ed..6a75c42e8 100644 --- a/apps/terminal/signal_handlers/session.py +++ b/apps/terminal/signal_handlers/session.py @@ -5,9 +5,11 @@ from terminal.models import 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: instance.cmd_amount = instance.compute_command_amount() + if instance.account_obj: + instance.account_obj.update_last_login_date() @receiver(post_save, sender=Session) @@ -16,4 +18,3 @@ def on_session_finished(sender, instance: Session, created, **kwargs): return # 清理一次可能因 task 未执行的缓存数据 Session.unlock_session(instance.id) - diff --git a/apps/users/views/profile/otp.py b/apps/users/views/profile/otp.py index aca1251fc..fce38a798 100644 --- a/apps/users/views/profile/otp.py +++ b/apps/users/views/profile/otp.py @@ -7,8 +7,8 @@ from django.http.response import HttpResponseRedirect from django.shortcuts import redirect from django.templatetags.static import static from django.urls import reverse -from django.utils.translation import gettext as _ 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.edit import FormView