perf: 修改用户确认

pull/11885/head
ibuler 2023-10-13 14:40:40 +08:00
parent 116d0ba5c6
commit 452ee1224c
7 changed files with 33 additions and 8 deletions

View File

@ -4,10 +4,12 @@ import time
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import status from rest_framework import status
from rest_framework.generics import RetrieveAPIView, CreateAPIView from rest_framework.decorators import action
from rest_framework.generics import RetrieveAPIView
from rest_framework.response import Response from rest_framework.response import Response
from authentication.permissions import UserConfirmation from authentication.permissions import UserConfirmation
from common.api import JMSGenericViewSet
from common.permissions import IsValidUser from common.permissions import IsValidUser
from ..const import ConfirmType from ..const import ConfirmType
from ..serializers import ConfirmSerializer from ..serializers import ConfirmSerializer
@ -20,10 +22,17 @@ class ConfirmBindORUNBindOAuth(RetrieveAPIView):
return Response('ok') return Response('ok')
class ConfirmApi(RetrieveAPIView, CreateAPIView): class UserConfirmationViewSet(JMSGenericViewSet):
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
serializer_class = ConfirmSerializer serializer_class = ConfirmSerializer
@action(methods=['get'], detail=False)
def check(self, request):
confirm_type = request.query_params.get('confirm_type', 'password')
permission = UserConfirmation.require(confirm_type)()
permission.has_permission(request, self)
return Response('ok')
def get_confirm_backend(self, confirm_type): def get_confirm_backend(self, confirm_type):
backend_classes = ConfirmType.get_prop_backends(confirm_type) backend_classes = ConfirmType.get_prop_backends(confirm_type)
if not backend_classes: if not backend_classes:
@ -34,12 +43,12 @@ class ConfirmApi(RetrieveAPIView, CreateAPIView):
continue continue
return backend return backend
def retrieve(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
confirm_type = request.query_params.get('confirm_type', 'password') confirm_type = request.query_params.get('confirm_type', 'password')
backend = self.get_confirm_backend(confirm_type) backend = self.get_confirm_backend(confirm_type)
if backend is None: if backend is None:
msg = _('This action require verify your MFA') msg = _('This action require verify your MFA')
return Response(data={'error': msg}, status=status.HTTP_404_NOT_FOUND) return Response(data={'error': msg}, status=status.HTTP_400_BAD_REQUEST)
data = { data = {
'confirm_type': backend.name, 'confirm_type': backend.name,

View File

@ -4,19 +4,30 @@ from django.shortcuts import render
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.viewsets import ModelViewSet
from authentication.mixins import AuthMixin from authentication.mixins import AuthMixin
from common.api import JMSModelViewSet
from .fido import register_begin, register_complete, auth_begin, auth_complete from .fido import register_begin, register_complete, auth_begin, auth_complete
from .models import Passkey from .models import Passkey
from .serializer import PasskeySerializer from .serializer import PasskeySerializer
from ...const import ConfirmType
from ...permissions import UserConfirmation
from ...views import FlashMessageMixin from ...views import FlashMessageMixin
class PasskeyViewSet(AuthMixin, FlashMessageMixin, ModelViewSet): class PasskeyViewSet(AuthMixin, FlashMessageMixin, JMSModelViewSet):
serializer_class = PasskeySerializer serializer_class = PasskeySerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
def get_permissions(self):
if self.is_swagger_request():
return super().get_permissions()
if self.action == 'register':
self.permission_classes = [
IsAuthenticated, UserConfirmation.require(ConfirmType.PASSWORD)
]
return super().get_permissions()
def get_queryset(self): def get_queryset(self):
return Passkey.objects.filter(user=self.request.user) return Passkey.objects.filter(user=self.request.user)

View File

@ -15,6 +15,7 @@ class ConfirmReLogin(BaseConfirm):
display_name = 'Re-Login' display_name = 'Re-Login'
def check(self): def check(self):
return True
return not self.user.is_password_authenticate() return not self.user.is_password_authenticate()
def authenticate(self, secret_key, mfa_type): def authenticate(self, secret_key, mfa_type):

View File

@ -19,6 +19,7 @@ class ConfirmType(TextChoices):
def get_can_confirm_types(cls, confirm_type): def get_can_confirm_types(cls, confirm_type):
start = cls.values.index(confirm_type) start = cls.values.index(confirm_type)
types = cls.values[start:] types = cls.values[start:]
types = [tp for tp in types if tp != 'password']
types.reverse() types.reverse()
return types return types

View File

@ -7,4 +7,4 @@ from ..const import ConfirmType, MFAType
class ConfirmSerializer(serializers.Serializer): class ConfirmSerializer(serializers.Serializer):
confirm_type = serializers.ChoiceField(required=True, allow_blank=True, choices=ConfirmType.choices) confirm_type = serializers.ChoiceField(required=True, allow_blank=True, choices=ConfirmType.choices)
mfa_type = serializers.ChoiceField(required=False, allow_blank=True, choices=MFAType.choices) mfa_type = serializers.ChoiceField(required=False, allow_blank=True, choices=MFAType.choices)
secret_key = EncryptedField(allow_blank=True) secret_key = EncryptedField(allow_blank=True, required=False)

View File

@ -13,6 +13,7 @@ router.register('sso', api.SSOViewSet, 'sso')
router.register('temp-tokens', api.TempTokenViewSet, 'temp-token') router.register('temp-tokens', api.TempTokenViewSet, 'temp-token')
router.register('connection-token', api.ConnectionTokenViewSet, 'connection-token') router.register('connection-token', api.ConnectionTokenViewSet, 'connection-token')
router.register('super-connection-token', api.SuperConnectionTokenViewSet, 'super-connection-token') router.register('super-connection-token', api.SuperConnectionTokenViewSet, 'super-connection-token')
router.register('confirm', api.UserConfirmationViewSet, 'confirm')
urlpatterns = [ urlpatterns = [
path('wecom/qr/unbind/', api.WeComQRUnBindForUserApi.as_view(), name='wecom-qr-unbind'), path('wecom/qr/unbind/', api.WeComQRUnBindForUserApi.as_view(), name='wecom-qr-unbind'),
@ -29,7 +30,6 @@ urlpatterns = [
name='feishu-event-subscription-callback'), 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.ConfirmApi.as_view(), name='user-confirm'),
path('confirm-oauth/', api.ConfirmBindORUNBindOAuth.as_view(), name='confirm-oauth'), path('confirm-oauth/', api.ConfirmBindORUNBindOAuth.as_view(), name='confirm-oauth'),
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'),

View File

@ -42,8 +42,11 @@ class ReferencedByOthers(JMSException):
class UserConfirmRequired(JMSException): class UserConfirmRequired(JMSException):
status_code = status.HTTP_412_PRECONDITION_FAILED
def __init__(self, code=None): def __init__(self, code=None):
detail = { detail = {
'type': 'user_confirm_required',
'code': code, 'code': code,
'detail': _('This action require confirm current user') 'detail': _('This action require confirm current user')
} }