perf: 用户确认和access key

Merge branch 'dev' of github.com:jumpserver/jumpserver into dev
pull/11841/head
老广 2023-10-13 16:37:45 +08:00 committed by GitHub
commit f80ff279d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 47 additions and 14 deletions

View File

@ -5,15 +5,18 @@ from django.utils.translation import gettext as _
from rest_framework import serializers from rest_framework import serializers
from rest_framework.response import Response from rest_framework.response import Response
from authentication.permissions import UserConfirmation
from common.api import JMSModelViewSet from common.api import JMSModelViewSet
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
from ..const import ConfirmType from ..const import ConfirmType
from ..serializers import AccessKeySerializer from ..permissions import UserConfirmation
from ..serializers import AccessKeySerializer, AccessKeyCreateSerializer
class AccessKeyViewSet(JMSModelViewSet): class AccessKeyViewSet(JMSModelViewSet):
serializer_class = AccessKeySerializer serializer_classes = {
'default': AccessKeySerializer,
'create': AccessKeyCreateSerializer
}
search_fields = ['^id'] search_fields = ['^id']
permission_classes = [RBACPermission] permission_classes = [RBACPermission]
@ -41,4 +44,5 @@ class AccessKeyViewSet(JMSModelViewSet):
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)
key = self.perform_create(serializer) key = self.perform_create(serializer)
return Response({'secret': key.secret, 'id': key.id}, status=201) serializer = self.get_serializer(instance=key)
return Response(serializer.data, status=201)

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

@ -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

@ -12,6 +12,7 @@ from ..models import AccessKey, TempToken
__all__ = [ __all__ = [
'AccessKeySerializer', 'BearerTokenSerializer', 'AccessKeySerializer', 'BearerTokenSerializer',
'SSOTokenSerializer', 'TempTokenSerializer', 'SSOTokenSerializer', 'TempTokenSerializer',
'AccessKeyCreateSerializer'
] ]
@ -22,6 +23,11 @@ class AccessKeySerializer(serializers.ModelSerializer):
read_only_fields = ['id', 'date_created', 'date_last_used'] read_only_fields = ['id', 'date_created', 'date_last_used']
class AccessKeyCreateSerializer(AccessKeySerializer):
class Meta(AccessKeySerializer.Meta):
fields = AccessKeySerializer.Meta.fields + ['secret']
class BearerTokenSerializer(serializers.Serializer): class BearerTokenSerializer(serializers.Serializer):
username = serializers.CharField(allow_null=True, required=False, write_only=True) username = serializers.CharField(allow_null=True, required=False, write_only=True)
password = serializers.CharField(write_only=True, allow_null=True, password = serializers.CharField(write_only=True, allow_null=True,

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')
} }

View File

@ -293,8 +293,8 @@ class ServiceAccountSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
from authentication.serializers import AccessKeySerializer from authentication.serializers import AccessKeyCreateSerializer
self.fields["access_key"] = AccessKeySerializer(read_only=True) self.fields["access_key"] = AccessKeyCreateSerializer(read_only=True)
def get_username(self): def get_username(self):
return self.initial_data.get("name") return self.initial_data.get("name")