mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-29 16:27:11 +00:00
perf: 优化confirm接口 (#8451)
* perf: 优化confirm接口 * perf: 修改 校验 * perf: 优化 confirm API 逻辑 * Delete django.po Co-authored-by: feng626 <1304903146@qq.com> Co-authored-by: ibuler <ibuler@qq.com> Co-authored-by: Jiangjie.Bai <bugatti_it@163.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>
This commit is contained in:
parent
ca19e45905
commit
a6cc8a8b05
@ -2,15 +2,16 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
from django.db.models import F, Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from common.drf.filters import BaseFilterSet
|
from common.drf.filters import BaseFilterSet
|
||||||
from common.drf.api import JMSBulkModelViewSet
|
from common.drf.api import JMSBulkModelViewSet
|
||||||
from common.mixins import RecordViewLogMixin
|
from common.mixins import RecordViewLogMixin
|
||||||
|
from common.permissions import UserConfirmation
|
||||||
|
from authentication.const import ConfirmType
|
||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
from assets.models import SystemUser
|
from assets.models import SystemUser
|
||||||
from ..models import Account
|
from ..models import Account
|
||||||
from ..hands import NeedMFAVerify
|
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ class SystemUserAppRelationViewSet(ApplicationAccountViewSet):
|
|||||||
|
|
||||||
class ApplicationAccountSecretViewSet(RecordViewLogMixin, ApplicationAccountViewSet):
|
class ApplicationAccountSecretViewSet(RecordViewLogMixin, ApplicationAccountViewSet):
|
||||||
serializer_class = serializers.AppAccountSecretSerializer
|
serializer_class = serializers.AppAccountSecretSerializer
|
||||||
permission_classes = [RBACPermission, NeedMFAVerify]
|
permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||||
http_method_names = ['get', 'options']
|
http_method_names = ['get', 'options']
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'retrieve': 'applications.view_applicationaccountsecret',
|
'retrieve': 'applications.view_applicationaccountsecret',
|
||||||
|
@ -11,5 +11,4 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from common.permissions import NeedMFAVerify
|
|
||||||
from users.models import User, UserGroup
|
from users.models import User, UserGroup
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from django.db.models import F, Q
|
from django.db.models import Q
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
@ -9,7 +9,8 @@ from orgs.mixins.api import OrgBulkModelViewSet
|
|||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
from common.drf.filters import BaseFilterSet
|
from common.drf.filters import BaseFilterSet
|
||||||
from common.mixins import RecordViewLogMixin
|
from common.mixins import RecordViewLogMixin
|
||||||
from common.permissions import NeedMFAVerify
|
from common.permissions import UserConfirmation
|
||||||
|
from authentication.const import ConfirmType
|
||||||
from ..tasks.account_connectivity import test_accounts_connectivity_manual
|
from ..tasks.account_connectivity import test_accounts_connectivity_manual
|
||||||
from ..models import AuthBook, Node
|
from ..models import AuthBook, Node
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
@ -88,7 +89,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
|||||||
'default': serializers.AccountSecretSerializer
|
'default': serializers.AccountSecretSerializer
|
||||||
}
|
}
|
||||||
http_method_names = ['get']
|
http_method_names = ['get']
|
||||||
permission_classes = [RBACPermission, NeedMFAVerify]
|
permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'list': 'assets.view_assetaccountsecret',
|
'list': 'assets.view_assetaccountsecret',
|
||||||
'retrieve': 'assets.view_assetaccountsecret',
|
'retrieve': 'assets.view_assetaccountsecret',
|
||||||
|
@ -277,7 +277,6 @@ def on_user_auth_success(sender, user, request, login_type=None, **kwargs):
|
|||||||
check_different_city_login_if_need(user, request)
|
check_different_city_login_if_need(user, request)
|
||||||
data = generate_data(user.username, request, login_type=login_type)
|
data = generate_data(user.username, request, login_type=login_type)
|
||||||
request.session['login_time'] = data['datetime'].strftime("%Y-%m-%d %H:%M:%S")
|
request.session['login_time'] = data['datetime'].strftime("%Y-%m-%d %H:%M:%S")
|
||||||
request.session["MFA_VERIFY_TIME"] = int(time.time())
|
|
||||||
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,85 +1,57 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.generics import ListCreateAPIView
|
from rest_framework.generics import RetrieveAPIView, CreateAPIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
from common.permissions import IsValidUser
|
from common.permissions import IsValidUser
|
||||||
from ..mfa import MFAOtp
|
|
||||||
from ..const import ConfirmType
|
from ..const import ConfirmType
|
||||||
from ..mixins import authenticate
|
|
||||||
from ..serializers import ConfirmSerializer
|
from ..serializers import ConfirmSerializer
|
||||||
|
|
||||||
|
|
||||||
class ConfirmViewSet(ListCreateAPIView):
|
class ConfirmApi(RetrieveAPIView, CreateAPIView):
|
||||||
permission_classes = (IsValidUser,)
|
permission_classes = (IsValidUser,)
|
||||||
serializer_class = ConfirmSerializer
|
serializer_class = ConfirmSerializer
|
||||||
|
|
||||||
def check(self, confirm_type: str):
|
def get_confirm_backend(self, confirm_type):
|
||||||
if confirm_type == ConfirmType.MFA:
|
backend_classes = ConfirmType.get_can_confirm_backend_classes(confirm_type)
|
||||||
return self.user.mfa_enabled
|
if not backend_classes:
|
||||||
|
return
|
||||||
|
for backend_cls in backend_classes:
|
||||||
|
backend = backend_cls(self.request.user, self.request)
|
||||||
|
if not backend.check():
|
||||||
|
continue
|
||||||
|
return backend
|
||||||
|
|
||||||
if confirm_type == ConfirmType.PASSWORD:
|
def retrieve(self, request, *args, **kwargs):
|
||||||
return self.user.is_password_authenticate()
|
confirm_type = request.query_params.get('confirm_type')
|
||||||
|
backend = self.get_confirm_backend(confirm_type)
|
||||||
|
if backend is None:
|
||||||
|
msg = _('This action require verify your MFA')
|
||||||
|
return Response(data={'error': msg}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if confirm_type == ConfirmType.RELOGIN:
|
data = {
|
||||||
return not self.user.is_password_authenticate()
|
'confirm_type': backend.name,
|
||||||
|
'content': backend.content,
|
||||||
def authenticate(self, confirm_type, secret_key):
|
}
|
||||||
if confirm_type == ConfirmType.MFA:
|
return Response(data=data)
|
||||||
ok, msg = MFAOtp(self.user).check_code(secret_key)
|
|
||||||
return ok, msg
|
|
||||||
|
|
||||||
if confirm_type == ConfirmType.PASSWORD:
|
|
||||||
ok = authenticate(self.request, username=self.user.username, password=secret_key)
|
|
||||||
msg = '' if ok else _('Authentication failed password incorrect')
|
|
||||||
return ok, msg
|
|
||||||
|
|
||||||
if confirm_type == ConfirmType.RELOGIN:
|
|
||||||
now = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
now = datetime.strptime(now, '%Y-%m-%d %H:%M:%S')
|
|
||||||
login_time = self.request.session.get('login_time')
|
|
||||||
SPECIFIED_TIME = 5
|
|
||||||
msg = _('Login time has exceeded {} minutes, please login again').format(SPECIFIED_TIME)
|
|
||||||
if not login_time:
|
|
||||||
return False, msg
|
|
||||||
login_time = datetime.strptime(login_time, '%Y-%m-%d %H:%M:%S')
|
|
||||||
if (now - login_time).seconds >= SPECIFIED_TIME * 60:
|
|
||||||
return False, msg
|
|
||||||
return True, ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def user(self):
|
|
||||||
return self.request.user
|
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
|
||||||
if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
|
|
||||||
return Response('ok')
|
|
||||||
|
|
||||||
mfa_verify_time = request.session.get('MFA_VERIFY_TIME', 0)
|
|
||||||
if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL:
|
|
||||||
return Response('ok')
|
|
||||||
|
|
||||||
data = []
|
|
||||||
for i, confirm_type in enumerate(ConfirmType.values, 1):
|
|
||||||
if self.check(confirm_type):
|
|
||||||
data.append({'name': confirm_type, 'level': i})
|
|
||||||
msg = _('This action require verify your MFA')
|
|
||||||
return Response({'error': msg, 'backends': data}, status=400)
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
validated_data = serializer.validated_data
|
validated_data = serializer.validated_data
|
||||||
|
|
||||||
confirm_type = validated_data.get('confirm_type')
|
confirm_type = validated_data.get('confirm_type')
|
||||||
|
mfa_type = validated_data.get('mfa_type')
|
||||||
secret_key = validated_data.get('secret_key')
|
secret_key = validated_data.get('secret_key')
|
||||||
ok, msg = self.authenticate(confirm_type, secret_key)
|
|
||||||
|
backend = self.get_confirm_backend(confirm_type)
|
||||||
|
ok, msg = backend.authenticate(secret_key, mfa_type)
|
||||||
if ok:
|
if ok:
|
||||||
request.session["MFA_VERIFY_TIME"] = int(time.time())
|
request.session['CONFIRM_LEVEL'] = ConfirmType.values.index(confirm_type) + 1
|
||||||
|
request.session['CONFIRM_TIME'] = int(time.time())
|
||||||
return Response('ok')
|
return Response('ok')
|
||||||
return Response({'error': msg}, status=400)
|
return Response({'error': msg}, status=400)
|
||||||
|
@ -2,10 +2,11 @@ from rest_framework.views import APIView
|
|||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from users.permissions import IsAuthConfirmTimeValid
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
from common.permissions import UserConfirmation
|
||||||
from common.mixins.api import RoleUserMixin, RoleAdminMixin
|
from common.mixins.api import RoleUserMixin, RoleAdminMixin
|
||||||
|
from authentication.const import ConfirmType
|
||||||
from authentication import errors
|
from authentication import errors
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
@ -26,7 +27,7 @@ class DingTalkQRUnBindBase(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class DingTalkQRUnBindForUserApi(RoleUserMixin, DingTalkQRUnBindBase):
|
class DingTalkQRUnBindForUserApi(RoleUserMixin, DingTalkQRUnBindBase):
|
||||||
permission_classes = (IsAuthConfirmTimeValid,)
|
permission_classes = (UserConfirmation.require(ConfirmType.ReLogin),)
|
||||||
|
|
||||||
|
|
||||||
class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase):
|
class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase):
|
||||||
|
@ -2,10 +2,11 @@ from rest_framework.views import APIView
|
|||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from users.permissions import IsAuthConfirmTimeValid
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
from common.permissions import UserConfirmation
|
||||||
from common.mixins.api import RoleUserMixin, RoleAdminMixin
|
from common.mixins.api import RoleUserMixin, RoleAdminMixin
|
||||||
|
from authentication.const import ConfirmType
|
||||||
from authentication import errors
|
from authentication import errors
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
@ -26,7 +27,7 @@ class FeiShuQRUnBindBase(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class FeiShuQRUnBindForUserApi(RoleUserMixin, FeiShuQRUnBindBase):
|
class FeiShuQRUnBindForUserApi(RoleUserMixin, FeiShuQRUnBindBase):
|
||||||
permission_classes = (IsAuthConfirmTimeValid,)
|
permission_classes = (UserConfirmation.require(ConfirmType.ReLogin),)
|
||||||
|
|
||||||
|
|
||||||
class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase):
|
class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase):
|
||||||
|
@ -10,22 +10,17 @@ from rest_framework.generics import CreateAPIView
|
|||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from common.permissions import IsValidUser, NeedMFAVerify
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.exceptions import UnexpectError
|
from common.exceptions import UnexpectError
|
||||||
from users.models.user import User
|
from users.models.user import User
|
||||||
from ..serializers import OtpVerifySerializer
|
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from .. import errors
|
from .. import errors
|
||||||
from ..mfa.otp import MFAOtp
|
|
||||||
from ..mixins import AuthMixin
|
from ..mixins import AuthMixin
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'MFAChallengeVerifyApi', 'UserOtpVerifyApi',
|
'MFAChallengeVerifyApi', 'MFASendCodeApi'
|
||||||
'MFASendCodeApi'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -88,30 +83,3 @@ class MFAChallengeVerifyApi(AuthMixin, CreateAPIView):
|
|||||||
raise ValidationError(data)
|
raise ValidationError(data)
|
||||||
except errors.NeedMoreInfoError as e:
|
except errors.NeedMoreInfoError as e:
|
||||||
return Response(e.as_data(), status=200)
|
return Response(e.as_data(), status=200)
|
||||||
|
|
||||||
|
|
||||||
class UserOtpVerifyApi(CreateAPIView):
|
|
||||||
permission_classes = (IsValidUser,)
|
|
||||||
serializer_class = OtpVerifySerializer
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return Response({'code': 'valid', 'msg': 'verified'})
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
code = serializer.validated_data["code"]
|
|
||||||
otp = MFAOtp(request.user)
|
|
||||||
|
|
||||||
ok, error = otp.check_code(code)
|
|
||||||
if ok:
|
|
||||||
request.session["MFA_VERIFY_TIME"] = int(time.time())
|
|
||||||
return Response({"ok": "1"})
|
|
||||||
else:
|
|
||||||
return Response({"error": _("Code is invalid, {}").format(error)}, status=400)
|
|
||||||
|
|
||||||
def get_permissions(self):
|
|
||||||
if self.request.method.lower() == 'get' \
|
|
||||||
and settings.SECURITY_VIEW_AUTH_NEED_MFA:
|
|
||||||
self.permission_classes = [NeedMFAVerify]
|
|
||||||
return super().get_permissions()
|
|
||||||
|
@ -2,10 +2,11 @@ from rest_framework.views import APIView
|
|||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from users.permissions import IsAuthConfirmTimeValid
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
from common.permissions import UserConfirmation
|
||||||
from common.mixins.api import RoleUserMixin, RoleAdminMixin
|
from common.mixins.api import RoleUserMixin, RoleAdminMixin
|
||||||
|
from authentication.const import ConfirmType
|
||||||
from authentication import errors
|
from authentication import errors
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
@ -26,7 +27,7 @@ class WeComQRUnBindBase(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class WeComQRUnBindForUserApi(RoleUserMixin, WeComQRUnBindBase):
|
class WeComQRUnBindForUserApi(RoleUserMixin, WeComQRUnBindBase):
|
||||||
permission_classes = (IsAuthConfirmTimeValid,)
|
permission_classes = (UserConfirmation.require(ConfirmType.ReLogin),)
|
||||||
|
|
||||||
|
|
||||||
class WeComQRUnBindForAdminApi(RoleAdminMixin, WeComQRUnBindBase):
|
class WeComQRUnBindForAdminApi(RoleAdminMixin, WeComQRUnBindBase):
|
||||||
|
5
apps/authentication/confirm/__init__.py
Normal file
5
apps/authentication/confirm/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from .mfa import ConfirmMFA
|
||||||
|
from .password import ConfirmPassword
|
||||||
|
from .relogin import ConfirmReLogin
|
||||||
|
|
||||||
|
CONFIRM_BACKENDS = [ConfirmReLogin, ConfirmPassword, ConfirmMFA]
|
30
apps/authentication/confirm/base.py
Normal file
30
apps/authentication/confirm/base.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import abc
|
||||||
|
|
||||||
|
|
||||||
|
class BaseConfirm(abc.ABC):
|
||||||
|
|
||||||
|
def __init__(self, user, request):
|
||||||
|
self.user = user
|
||||||
|
self.request = request
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def name(self) -> str:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def display_name(self) -> str:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def check(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def authenticate(self, secret_key, mfa_type) -> tuple:
|
||||||
|
return False, 'Error msg'
|
26
apps/authentication/confirm/mfa.py
Normal file
26
apps/authentication/confirm/mfa.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from users.models import User
|
||||||
|
|
||||||
|
from .base import BaseConfirm
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmMFA(BaseConfirm):
|
||||||
|
name = 'mfa'
|
||||||
|
display_name = 'MFA'
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
return self.user.active_mfa_backends
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
backends = User.get_user_mfa_backends(self.user)
|
||||||
|
return [{
|
||||||
|
'name': backend.name,
|
||||||
|
'disabled': not bool(backend.is_active()),
|
||||||
|
'display_name': backend.display_name,
|
||||||
|
'placeholder': backend.placeholder,
|
||||||
|
} for backend in backends]
|
||||||
|
|
||||||
|
def authenticate(self, secret_key, mfa_type):
|
||||||
|
mfa_backend = self.user.get_mfa_backend_by_type(mfa_type)
|
||||||
|
ok, msg = mfa_backend.check_code(secret_key)
|
||||||
|
return ok, msg
|
17
apps/authentication/confirm/password.py
Normal file
17
apps/authentication/confirm/password.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from authentication.mixins import authenticate
|
||||||
|
from .base import BaseConfirm
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmPassword(BaseConfirm):
|
||||||
|
name = 'password'
|
||||||
|
display_name = _('Password')
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
return self.user.is_password_authenticate()
|
||||||
|
|
||||||
|
def authenticate(self, secret_key, mfa_type):
|
||||||
|
ok = authenticate(self.request, username=self.user.username, password=secret_key)
|
||||||
|
msg = '' if ok else _('Authentication failed password incorrect')
|
||||||
|
return ok, msg
|
30
apps/authentication/confirm/relogin.py
Normal file
30
apps/authentication/confirm/relogin.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from .base import BaseConfirm
|
||||||
|
|
||||||
|
SPECIFIED_TIME = 5
|
||||||
|
|
||||||
|
RELOGIN_ERROR = _('Login time has exceeded {} minutes, please login again').format(SPECIFIED_TIME)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmReLogin(BaseConfirm):
|
||||||
|
name = 'relogin'
|
||||||
|
display_name = 'Re-Login'
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
return not self.user.is_password_authenticate()
|
||||||
|
|
||||||
|
def authenticate(self, secret_key, mfa_type):
|
||||||
|
now = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
now = datetime.strptime(now, '%Y-%m-%d %H:%M:%S')
|
||||||
|
login_time = self.request.session.get('login_time')
|
||||||
|
msg = RELOGIN_ERROR
|
||||||
|
if not login_time:
|
||||||
|
return False, msg
|
||||||
|
login_time = datetime.strptime(login_time, '%Y-%m-%d %H:%M:%S')
|
||||||
|
if (now - login_time).seconds >= SPECIFIED_TIME * 60:
|
||||||
|
return False, msg
|
||||||
|
return True, ''
|
@ -1,10 +1,35 @@
|
|||||||
from django.db.models import TextChoices
|
from django.db.models import TextChoices
|
||||||
|
|
||||||
|
from authentication.confirm import CONFIRM_BACKENDS
|
||||||
|
from .confirm import ConfirmMFA, ConfirmPassword, ConfirmReLogin
|
||||||
|
from .mfa import MFAOtp, MFASms, MFARadius
|
||||||
|
|
||||||
RSA_PRIVATE_KEY = 'rsa_private_key'
|
RSA_PRIVATE_KEY = 'rsa_private_key'
|
||||||
RSA_PUBLIC_KEY = 'rsa_public_key'
|
RSA_PUBLIC_KEY = 'rsa_public_key'
|
||||||
|
|
||||||
|
CONFIRM_BACKEND_MAP = {backend.name: backend for backend in CONFIRM_BACKENDS}
|
||||||
|
|
||||||
|
|
||||||
class ConfirmType(TextChoices):
|
class ConfirmType(TextChoices):
|
||||||
RELOGIN = 'relogin', 'Re-Login'
|
ReLogin = ConfirmReLogin.name, ConfirmReLogin.display_name
|
||||||
PASSWORD = 'password', 'Password'
|
PASSWORD = ConfirmPassword.name, ConfirmPassword.display_name
|
||||||
MFA = 'mfa', 'MFA'
|
MFA = ConfirmMFA.name, ConfirmMFA.display_name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_can_confirm_types(cls, confirm_type):
|
||||||
|
start = cls.values.index(confirm_type)
|
||||||
|
return cls.values[start:]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_can_confirm_backend_classes(cls, confirm_type):
|
||||||
|
types = cls.get_can_confirm_types(confirm_type)
|
||||||
|
backend_classes = [
|
||||||
|
CONFIRM_BACKEND_MAP[tp] for tp in types if tp in CONFIRM_BACKEND_MAP
|
||||||
|
]
|
||||||
|
return backend_classes
|
||||||
|
|
||||||
|
|
||||||
|
class MFAType(TextChoices):
|
||||||
|
OTP = MFAOtp.name, MFAOtp.display_name
|
||||||
|
SMS = MFASms.name, MFASms.display_name
|
||||||
|
Radius = MFARadius.name, MFARadius.display_name
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.drf.fields import EncryptedField
|
from common.drf.fields import EncryptedField
|
||||||
from ..const import ConfirmType
|
from ..const import ConfirmType, MFAType
|
||||||
|
|
||||||
|
|
||||||
class ConfirmSerializer(serializers.Serializer):
|
class ConfirmSerializer(serializers.Serializer):
|
||||||
confirm_type = serializers.ChoiceField(
|
confirm_type = serializers.ChoiceField(required=True, allow_blank=True, choices=ConfirmType.choices)
|
||||||
required=True, choices=ConfirmType.choices
|
mfa_type = serializers.ChoiceField(required=False, allow_blank=True, choices=MFAType.choices)
|
||||||
)
|
|
||||||
secret_key = EncryptedField()
|
secret_key = EncryptedField()
|
||||||
|
@ -4,9 +4,8 @@ from rest_framework import serializers
|
|||||||
|
|
||||||
from common.drf.fields import EncryptedField
|
from common.drf.fields import EncryptedField
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'OtpVerifySerializer', 'MFAChallengeSerializer', 'MFASelectTypeSerializer',
|
'MFAChallengeSerializer', 'MFASelectTypeSerializer',
|
||||||
'PasswordVerifySerializer',
|
'PasswordVerifySerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -29,7 +28,3 @@ class MFAChallengeSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OtpVerifySerializer(serializers.Serializer):
|
|
||||||
code = serializers.CharField(max_length=6, min_length=6)
|
|
||||||
|
@ -26,13 +26,12 @@ urlpatterns = [
|
|||||||
path('feishu/event/subscription/callback/', api.FeiShuEventSubscriptionCallback.as_view(), name='feishu-event-subscription-callback'),
|
path('feishu/event/subscription/callback/', api.FeiShuEventSubscriptionCallback.as_view(), name='feishu-event-subscription-callback'),
|
||||||
|
|
||||||
path('auth/', api.TokenCreateApi.as_view(), name='user-auth'),
|
path('auth/', api.TokenCreateApi.as_view(), name='user-auth'),
|
||||||
path('confirm/', api.ConfirmViewSet.as_view(), name='user-confirm'),
|
path('confirm/', api.ConfirmApi.as_view(), name='user-confirm'),
|
||||||
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),
|
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),
|
||||||
path('mfa/verify/', api.MFAChallengeVerifyApi.as_view(), name='mfa-verify'),
|
path('mfa/verify/', api.MFAChallengeVerifyApi.as_view(), name='mfa-verify'),
|
||||||
path('mfa/challenge/', api.MFAChallengeVerifyApi.as_view(), name='mfa-challenge'),
|
path('mfa/challenge/', api.MFAChallengeVerifyApi.as_view(), name='mfa-challenge'),
|
||||||
path('mfa/select/', api.MFASendCodeApi.as_view(), name='mfa-select'),
|
path('mfa/select/', api.MFASendCodeApi.as_view(), name='mfa-select'),
|
||||||
path('mfa/send-code/', api.MFASendCodeApi.as_view(), name='mfa-send-codej'),
|
path('mfa/send-code/', api.MFASendCodeApi.as_view(), name='mfa-send-codej'),
|
||||||
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
|
|
||||||
path('password/verify/', api.UserPasswordVerifyApi.as_view(), name='user-password-verify'),
|
path('password/verify/', api.UserPasswordVerifyApi.as_view(), name='user-password-verify'),
|
||||||
path('login-confirm-ticket/status/', api.TicketStatusApi.as_view(), name='login-confirm-ticket-status'),
|
path('login-confirm-ticket/status/', api.TicketStatusApi.as_view(), name='login-confirm-ticket-status'),
|
||||||
]
|
]
|
||||||
|
@ -8,17 +8,17 @@ from django.db.utils import IntegrityError
|
|||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
from users.views import UserVerifyPasswordView
|
|
||||||
from users.utils import is_auth_confirm_time_valid
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from users.permissions import IsAuthConfirmTimeValid
|
from users.views import UserVerifyPasswordView
|
||||||
from common.utils import get_logger, FlashMessageUtil
|
from common.utils import get_logger, FlashMessageUtil
|
||||||
from common.utils.random import random_string
|
from common.utils.random import random_string
|
||||||
from common.utils.django import reverse, get_object_or_none
|
from common.utils.django import reverse, get_object_or_none
|
||||||
from common.sdk.im.dingtalk import URL
|
from common.sdk.im.dingtalk import URL
|
||||||
from common.mixins.views import PermissionsMixin
|
from common.mixins.views import UserConfirmRequiredExceptionMixin, PermissionsMixin
|
||||||
|
from common.permissions import UserConfirmation
|
||||||
from authentication import errors
|
from authentication import errors
|
||||||
from authentication.mixins import AuthMixin
|
from authentication.mixins import AuthMixin
|
||||||
|
from authentication.const import ConfirmType
|
||||||
from common.sdk.im.dingtalk import DingTalk
|
from common.sdk.im.dingtalk import DingTalk
|
||||||
from common.utils.common import get_request_ip
|
from common.utils.common import get_request_ip
|
||||||
from authentication.notifications import OAuthBindMessage
|
from authentication.notifications import OAuthBindMessage
|
||||||
@ -30,7 +30,7 @@ logger = get_logger(__file__)
|
|||||||
DINGTALK_STATE_SESSION_KEY = '_dingtalk_state'
|
DINGTALK_STATE_SESSION_KEY = '_dingtalk_state'
|
||||||
|
|
||||||
|
|
||||||
class DingTalkBaseMixin(PermissionsMixin, View):
|
class DingTalkBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
@ -119,7 +119,7 @@ class DingTalkOAuthMixin(DingTalkBaseMixin, View):
|
|||||||
|
|
||||||
|
|
||||||
class DingTalkQRBindView(DingTalkQRMixin, View):
|
class DingTalkQRBindView(DingTalkQRMixin, View):
|
||||||
permission_classes = (IsAuthenticated, IsAuthConfirmTimeValid)
|
permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.ReLogin))
|
||||||
|
|
||||||
def get(self, request: HttpRequest):
|
def get(self, request: HttpRequest):
|
||||||
user = request.user
|
user = request.user
|
||||||
|
@ -8,16 +8,17 @@ from django.db.utils import IntegrityError
|
|||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
from users.permissions import IsAuthConfirmTimeValid
|
|
||||||
from users.views import UserVerifyPasswordView
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
from users.views import UserVerifyPasswordView
|
||||||
from common.utils import get_logger, FlashMessageUtil
|
from common.utils import get_logger, FlashMessageUtil
|
||||||
from common.utils.random import random_string
|
from common.utils.random import random_string
|
||||||
from common.utils.django import reverse, get_object_or_none
|
from common.utils.django import reverse, get_object_or_none
|
||||||
from common.mixins.views import PermissionsMixin
|
from common.mixins.views import UserConfirmRequiredExceptionMixin, PermissionsMixin
|
||||||
|
from common.permissions import UserConfirmation
|
||||||
from common.sdk.im.feishu import FeiShu, URL
|
from common.sdk.im.feishu import FeiShu, URL
|
||||||
from common.utils.common import get_request_ip
|
from common.utils.common import get_request_ip
|
||||||
from authentication import errors
|
from authentication import errors
|
||||||
|
from authentication.const import ConfirmType
|
||||||
from authentication.mixins import AuthMixin
|
from authentication.mixins import AuthMixin
|
||||||
from authentication.notifications import OAuthBindMessage
|
from authentication.notifications import OAuthBindMessage
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ logger = get_logger(__file__)
|
|||||||
FEISHU_STATE_SESSION_KEY = '_feishu_state'
|
FEISHU_STATE_SESSION_KEY = '_feishu_state'
|
||||||
|
|
||||||
|
|
||||||
class FeiShuQRMixin(PermissionsMixin, View):
|
class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
@ -89,7 +90,7 @@ class FeiShuQRMixin(PermissionsMixin, View):
|
|||||||
|
|
||||||
|
|
||||||
class FeiShuQRBindView(FeiShuQRMixin, View):
|
class FeiShuQRBindView(FeiShuQRMixin, View):
|
||||||
permission_classes = (IsAuthenticated, IsAuthConfirmTimeValid)
|
permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.ReLogin))
|
||||||
|
|
||||||
def get(self, request: HttpRequest):
|
def get(self, request: HttpRequest):
|
||||||
user = request.user
|
user = request.user
|
||||||
|
@ -8,18 +8,19 @@ from django.db.utils import IntegrityError
|
|||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
from users.views import UserVerifyPasswordView
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from users.permissions import IsAuthConfirmTimeValid
|
from users.views import UserVerifyPasswordView
|
||||||
from common.utils import get_logger, FlashMessageUtil
|
from common.utils import get_logger, FlashMessageUtil
|
||||||
from common.utils.random import random_string
|
from common.utils.random import random_string
|
||||||
from common.utils.django import reverse, get_object_or_none
|
from common.utils.django import reverse, get_object_or_none
|
||||||
from common.sdk.im.wecom import URL
|
from common.sdk.im.wecom import URL
|
||||||
from common.sdk.im.wecom import WeCom
|
from common.sdk.im.wecom import WeCom
|
||||||
from common.mixins.views import PermissionsMixin
|
from common.mixins.views import UserConfirmRequiredExceptionMixin, PermissionsMixin
|
||||||
from common.utils.common import get_request_ip
|
from common.utils.common import get_request_ip
|
||||||
|
from common.permissions import UserConfirmation
|
||||||
from authentication import errors
|
from authentication import errors
|
||||||
from authentication.mixins import AuthMixin
|
from authentication.mixins import AuthMixin
|
||||||
|
from authentication.const import ConfirmType
|
||||||
from authentication.notifications import OAuthBindMessage
|
from authentication.notifications import OAuthBindMessage
|
||||||
from .mixins import METAMixin
|
from .mixins import METAMixin
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ logger = get_logger(__file__)
|
|||||||
WECOM_STATE_SESSION_KEY = '_wecom_state'
|
WECOM_STATE_SESSION_KEY = '_wecom_state'
|
||||||
|
|
||||||
|
|
||||||
class WeComBaseMixin(PermissionsMixin, View):
|
class WeComBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
@ -118,7 +119,7 @@ class WeComOAuthMixin(WeComBaseMixin, View):
|
|||||||
|
|
||||||
|
|
||||||
class WeComQRBindView(WeComQRMixin, View):
|
class WeComQRBindView(WeComQRMixin, View):
|
||||||
permission_classes = (IsAuthenticated, IsAuthConfirmTimeValid)
|
permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.ReLogin))
|
||||||
|
|
||||||
def get(self, request: HttpRequest):
|
def get(self, request: HttpRequest):
|
||||||
user = request.user
|
user = request.user
|
||||||
|
@ -41,10 +41,13 @@ class ReferencedByOthers(JMSException):
|
|||||||
default_detail = _('Is referenced by other objects and cannot be deleted')
|
default_detail = _('Is referenced by other objects and cannot be deleted')
|
||||||
|
|
||||||
|
|
||||||
class MFAVerifyRequired(JMSException):
|
class UserConfirmRequired(JMSException):
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
def __init__(self, code=None):
|
||||||
default_code = 'mfa_verify_required'
|
detail = {
|
||||||
default_detail = _('This action require verify your MFA')
|
'code': code,
|
||||||
|
'detail': _('This action require confirm current user')
|
||||||
|
}
|
||||||
|
super().__init__(detail=detail, code=code)
|
||||||
|
|
||||||
|
|
||||||
class UnexpectError(JMSException):
|
class UnexpectError(JMSException):
|
||||||
|
@ -2,16 +2,26 @@
|
|||||||
#
|
#
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
|
from django.http.response import JsonResponse
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from common.permissions import IsValidUser
|
from common.exceptions import UserConfirmRequired
|
||||||
from audits.utils import create_operate_log
|
from audits.utils import create_operate_log
|
||||||
from audits.models import OperateLog
|
from audits.models import OperateLog
|
||||||
|
|
||||||
__all__ = ["PermissionsMixin", "RecordViewLogMixin"]
|
__all__ = ["PermissionsMixin", "RecordViewLogMixin", "UserConfirmRequiredExceptionMixin"]
|
||||||
|
|
||||||
|
|
||||||
|
class UserConfirmRequiredExceptionMixin:
|
||||||
|
"""
|
||||||
|
异常处理
|
||||||
|
"""
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
except UserConfirmRequired as e:
|
||||||
|
return JsonResponse(e.detail, status=e.status_code)
|
||||||
|
|
||||||
|
|
||||||
class PermissionsMixin(UserPassesTestMixin):
|
class PermissionsMixin(UserPassesTestMixin):
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import time
|
import time
|
||||||
from rest_framework import permissions
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from common.exceptions import MFAVerifyRequired
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
from authentication.const import ConfirmType
|
||||||
|
from common.exceptions import UserConfirmRequired
|
||||||
|
|
||||||
|
|
||||||
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
|
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
|
||||||
@ -29,18 +32,23 @@ class WithBootstrapToken(permissions.BasePermission):
|
|||||||
return settings.BOOTSTRAP_TOKEN == request_bootstrap_token
|
return settings.BOOTSTRAP_TOKEN == request_bootstrap_token
|
||||||
|
|
||||||
|
|
||||||
class NeedMFAVerify(permissions.BasePermission):
|
class UserConfirmation(permissions.BasePermission):
|
||||||
|
ttl = 300
|
||||||
|
min_level = 1
|
||||||
|
confirm_type = ConfirmType.ReLogin
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
|
confirm_level = request.session.get('CONFIRM_LEVEL')
|
||||||
return True
|
confirm_time = request.session.get('CONFIRM_TIME')
|
||||||
|
|
||||||
mfa_verify_time = request.session.get('MFA_VERIFY_TIME', 0)
|
if not confirm_level or not confirm_time or \
|
||||||
if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL:
|
confirm_level < self.min_level or \
|
||||||
return True
|
confirm_time < time.time() - self.ttl:
|
||||||
raise MFAVerifyRequired()
|
raise UserConfirmRequired(code=self.confirm_type)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
class IsObjectOwner(IsValidUser):
|
def require(cls, confirm_type=ConfirmType.ReLogin, ttl=300):
|
||||||
def has_object_permission(self, request, view, obj):
|
min_level = ConfirmType.values.index(confirm_type) + 1
|
||||||
return (super().has_object_permission(request, view, obj) and
|
name = 'UserConfirmationLevel{}TTL{}'.format(min_level, ttl)
|
||||||
request.user == getattr(obj, 'user', None))
|
return type(name, (cls,), {'min_level': min_level, 'ttl': ttl, 'confirm_type': confirm_type})
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:896e5302ad4e775f9160b51385466525e67f0e5a0a8cc10981ea843626114494
|
oid sha256:50095e2b80c1235a812856f709061dc171509a3883752af48b3c1bb0c112f025
|
||||||
size 125939
|
size 259
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:521d1a529b430f1ac6a519f9c659abe79f25fc9b05a03519f4abe945ae2d1972
|
oid sha256:e4ec3219881a5a03c49f988a0ea48668ff8403356d210b7e7c6fbb9e8be26a6b
|
||||||
size 103946
|
size 259
|
||||||
|
@ -1754,30 +1754,14 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}"
|
|||||||
msgid "{ApplicationPermission} REMOVE {SystemUser}"
|
msgid "{ApplicationPermission} REMOVE {SystemUser}"
|
||||||
msgstr "{ApplicationPermission} 移除 {SystemUser}"
|
msgstr "{ApplicationPermission} 移除 {SystemUser}"
|
||||||
|
|
||||||
#: authentication/api/confirm.py:40
|
|
||||||
msgid "Authentication failed password incorrect"
|
|
||||||
msgstr "认证失败 (用户名或密码不正确)"
|
|
||||||
|
|
||||||
#: authentication/api/confirm.py:48
|
|
||||||
msgid "Login time has exceeded {} minutes, please login again"
|
|
||||||
msgstr "登录时长已超过 {} 分钟,请重新登录"
|
|
||||||
|
|
||||||
#: authentication/api/confirm.py:72 common/exceptions.py:47
|
|
||||||
msgid "This action require verify your MFA"
|
|
||||||
msgstr "这个操作需要验证 MFA"
|
|
||||||
|
|
||||||
#: authentication/api/connection_token.py:326
|
#: authentication/api/connection_token.py:326
|
||||||
msgid "Invalid token"
|
msgid "Invalid token"
|
||||||
msgstr "无效的令牌"
|
msgstr "无效的令牌"
|
||||||
|
|
||||||
#: authentication/api/mfa.py:64
|
#: authentication/api/mfa.py:59
|
||||||
msgid "Current user not support mfa type: {}"
|
msgid "Current user not support mfa type: {}"
|
||||||
msgstr "当前用户不支持 MFA 类型: {}"
|
msgstr "当前用户不支持 MFA 类型: {}"
|
||||||
|
|
||||||
#: authentication/api/mfa.py:111
|
|
||||||
msgid "Code is invalid, {}"
|
|
||||||
msgstr "验证码无效: {}"
|
|
||||||
|
|
||||||
#: authentication/apps.py:7
|
#: authentication/apps.py:7
|
||||||
msgid "Authentication"
|
msgid "Authentication"
|
||||||
msgstr "认证"
|
msgstr "认证"
|
||||||
@ -1833,6 +1817,15 @@ msgstr "无效的令牌头。符号字符串不应包含无效字符。"
|
|||||||
msgid "Invalid token or cache refreshed."
|
msgid "Invalid token or cache refreshed."
|
||||||
msgstr "刷新的令牌或缓存无效。"
|
msgstr "刷新的令牌或缓存无效。"
|
||||||
|
|
||||||
|
#: authentication/confirm/password.py:17
|
||||||
|
msgid "Authentication failed password incorrect"
|
||||||
|
msgstr "认证失败 (用户名或密码不正确)"
|
||||||
|
|
||||||
|
#: authentication/confirm/relogin.py:10
|
||||||
|
msgid "Login time has exceeded {} minutes, please login again"
|
||||||
|
msgstr "登录时长已超过 {} 分钟,请重新登录"
|
||||||
|
|
||||||
|
#: authentication/errors.py:26
|
||||||
#: authentication/errors/const.py:18
|
#: authentication/errors/const.py:18
|
||||||
msgid "Username/password check failed"
|
msgid "Username/password check failed"
|
||||||
msgstr "用户名/密码 校验失败"
|
msgstr "用户名/密码 校验失败"
|
||||||
@ -2561,7 +2554,11 @@ msgstr "多对多反向是不被允许的"
|
|||||||
msgid "Is referenced by other objects and cannot be deleted"
|
msgid "Is referenced by other objects and cannot be deleted"
|
||||||
msgstr "被其他对象关联,不能删除"
|
msgstr "被其他对象关联,不能删除"
|
||||||
|
|
||||||
#: common/exceptions.py:53
|
#: common/exceptions.py:46
|
||||||
|
msgid "This action require confirm current user"
|
||||||
|
msgstr "此操作需要确认当前用户"
|
||||||
|
|
||||||
|
#: common/exceptions.py:52
|
||||||
msgid "Unexpect error occur"
|
msgid "Unexpect error occur"
|
||||||
msgstr "发生意外错误"
|
msgstr "发生意外错误"
|
||||||
|
|
||||||
@ -6653,3 +6650,6 @@ msgstr "旗舰版"
|
|||||||
#: xpack/plugins/license/models.py:77
|
#: xpack/plugins/license/models.py:77
|
||||||
msgid "Community edition"
|
msgid "Community edition"
|
||||||
msgstr "社区版"
|
msgstr "社区版"
|
||||||
|
|
||||||
|
#~ msgid "Code is invalid, {}"
|
||||||
|
#~ msgstr "验证码无效: {}"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
from .utils import is_auth_password_time_valid, is_auth_confirm_time_valid
|
from .utils import is_auth_password_time_valid
|
||||||
|
|
||||||
|
|
||||||
class IsAuthPasswdTimeValid(permissions.IsAuthenticated):
|
class IsAuthPasswdTimeValid(permissions.IsAuthenticated):
|
||||||
@ -8,10 +8,3 @@ class IsAuthPasswdTimeValid(permissions.IsAuthenticated):
|
|||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
return super().has_permission(request, view) \
|
return super().has_permission(request, view) \
|
||||||
and is_auth_password_time_valid(request.session)
|
and is_auth_password_time_valid(request.session)
|
||||||
|
|
||||||
|
|
||||||
class IsAuthConfirmTimeValid(permissions.IsAuthenticated):
|
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
|
||||||
return super().has_permission(request, view) \
|
|
||||||
and is_auth_confirm_time_valid(request.session)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user