mirror of https://github.com/jumpserver/jumpserver
perf: 用户确认和access key
Merge branch 'dev' of github.com:jumpserver/jumpserver into devpull/11841/head
commit
f80ff279d0
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue