Merge pull request #11552 from jumpserver/pr@dev@add_api_check_for_unauth

perf: 添加 check api,检测所有 api
pull/11553/head
老广 2023-09-13 17:24:40 +08:00 committed by GitHub
commit 7c973616cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 190 additions and 40 deletions

View File

@ -19,7 +19,9 @@ class AccountsTaskCreateAPI(CreateAPIView):
code = 'accounts.push_account'
else:
code = 'accounts.verify_account'
return request.user.has_perm(code)
has = request.user.has_perm(code)
if not has:
self.permission_denied(request)
def perform_create(self, serializer):
data = serializer.validated_data
@ -44,6 +46,6 @@ class AccountsTaskCreateAPI(CreateAPIView):
def get_exception_handler(self):
def handler(e, context):
return Response({"error": str(e)}, status=400)
return Response({"error": str(e)}, status=401)
return handler

View File

@ -1,14 +1,14 @@
# ~*~ coding: utf-8 ~*~
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from django.utils.translation import gettext_lazy as _
from assets.locks import NodeAddChildrenLock
from common.exceptions import JMSException
from common.tree import TreeNodeSerializer
from common.utils import get_logger
from common.exceptions import JMSException
from orgs.mixins import generics
from orgs.utils import current_org
from .mixin import SerializeToTreeNodeMixin
@ -35,8 +35,8 @@ class NodeChildrenApi(generics.ListCreateAPIView):
is_initial = False
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
self.instance = self.get_object()
return super().initial(request, *args, **kwargs)
def perform_create(self, serializer):
with NodeAddChildrenLock(self.instance):

View File

@ -13,7 +13,7 @@ from ..serializers import ConfirmSerializer
class ConfirmBindORUNBindOAuth(RetrieveAPIView):
permission_classes = (UserConfirmation.require(ConfirmType.ReLogin),)
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.ReLogin),)
def retrieve(self, request, *args, **kwargs):
return Response('ok')

View File

@ -1,13 +1,13 @@
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from users.models import User
from common.utils import get_logger
from common.permissions import UserConfirmation
from common.api import RoleUserMixin, RoleAdminMixin
from authentication.const import ConfirmType
from authentication import errors
from authentication.const import ConfirmType
from common.api import RoleUserMixin, RoleAdminMixin
from common.permissions import UserConfirmation, IsValidUser
from common.utils import get_logger
from users.models import User
logger = get_logger(__file__)
@ -27,7 +27,7 @@ class DingTalkQRUnBindBase(APIView):
class DingTalkQRUnBindForUserApi(RoleUserMixin, DingTalkQRUnBindBase):
permission_classes = (UserConfirmation.require(ConfirmType.ReLogin),)
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.ReLogin),)
class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase):

View File

@ -1,13 +1,13 @@
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from users.models import User
from common.utils import get_logger
from common.permissions import UserConfirmation
from common.api import RoleUserMixin, RoleAdminMixin
from authentication.const import ConfirmType
from authentication import errors
from authentication.const import ConfirmType
from common.api import RoleUserMixin, RoleAdminMixin
from common.permissions import UserConfirmation, IsValidUser
from common.utils import get_logger
from users.models import User
logger = get_logger(__file__)
@ -27,7 +27,7 @@ class FeiShuQRUnBindBase(APIView):
class FeiShuQRUnBindForUserApi(RoleUserMixin, FeiShuQRUnBindBase):
permission_classes = (UserConfirmation.require(ConfirmType.ReLogin),)
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.ReLogin),)
class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase):
@ -38,7 +38,7 @@ class FeiShuEventSubscriptionCallback(APIView):
"""
# https://open.feishu.cn/document/ukTMukTMukTM/uUTNz4SN1MjL1UzM
"""
permission_classes = ()
permission_classes = (IsValidUser,)
def post(self, request: Request, *args, **kwargs):
return Response(data=request.data)

View File

@ -3,6 +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
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
@ -13,6 +14,7 @@ from common.utils import get_logger
from users.models.user import User
from .. import errors
from .. import serializers
from ..errors import SessionEmptyError
from ..mixins import AuthMixin
logger = get_logger(__name__)
@ -56,6 +58,7 @@ class MFASendCodeApi(AuthMixin, CreateAPIView):
if not mfa_backend or not mfa_backend.challenge_required:
error = _('Current user not support mfa type: {}').format(mfa_type)
raise ValidationError({'error': error})
try:
mfa_backend.send_challenge()
except Exception as e:
@ -66,6 +69,15 @@ class MFAChallengeVerifyApi(AuthMixin, CreateAPIView):
permission_classes = (AllowAny,)
serializer_class = serializers.MFAChallengeSerializer
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
try:
user = self.get_user_from_session()
except SessionEmptyError:
user = None
if not user:
raise exceptions.NotAuthenticated()
def perform_create(self, serializer):
user = self.get_user_from_session()
code = serializer.validated_data.get('code')

View File

@ -1,26 +1,27 @@
from uuid import UUID
from urllib.parse import urlencode
from uuid import UUID
from django.contrib.auth import login
from django.conf import settings
from django.contrib.auth import login
from django.http.response import HttpResponseRedirect
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.response import Response
from common.utils.timezone import utc_now
from common.const.http import POST, GET
from common.api import JMSGenericViewSet
from common.serializers import EmptySerializer
from common.const.http import POST, GET
from common.permissions import OnlySuperUser
from common.serializers import EmptySerializer
from common.utils import reverse
from common.utils.timezone import utc_now
from users.models import User
from ..serializers import SSOTokenSerializer
from ..models import SSOToken
from ..errors import SSOAuthClosed
from ..filters import AuthKeyQueryDeclaration
from ..mixins import AuthMixin
from ..errors import SSOAuthClosed
from ..models import SSOToken
from ..serializers import SSOTokenSerializer
NEXT_URL = 'next'
AUTH_KEY = 'authkey'
@ -67,6 +68,9 @@ class SSOViewSet(AuthMixin, JMSGenericViewSet):
if not next_url or not next_url.startswith('/'):
next_url = reverse('index')
if not authkey:
raise serializers.ValidationError("authkey is required")
try:
authkey = UUID(authkey)
token = SSOToken.objects.get(authkey=authkey, expired=False)

View File

@ -1,13 +1,13 @@
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from users.models import User
from common.utils import get_logger
from common.permissions import UserConfirmation
from common.api import RoleUserMixin, RoleAdminMixin
from authentication.const import ConfirmType
from authentication import errors
from authentication.const import ConfirmType
from common.api import RoleUserMixin, RoleAdminMixin
from common.permissions import UserConfirmation, IsValidUser
from common.utils import get_logger
from users.models import User
logger = get_logger(__file__)
@ -27,7 +27,7 @@ class WeComQRUnBindBase(APIView):
class WeComQRUnBindForUserApi(RoleUserMixin, WeComQRUnBindBase):
permission_classes = (UserConfirmation.require(ConfirmType.ReLogin),)
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.ReLogin),)
class WeComQRUnBindForAdminApi(RoleAdminMixin, WeComQRUnBindBase):

View File

@ -8,7 +8,8 @@ class PassthroughRenderer(renderers.BaseRenderer):
"""
Return data as-is. View should supply a Response.
"""
media_type = ''
media_type = 'application/octet-stream'
format = ''
def render(self, data, accepted_media_type=None, renderer_context=None):
return data

View File

@ -0,0 +1,119 @@
import re
from django.conf import settings
from django.core.management.base import BaseCommand
from django.test import Client
from django.urls import URLPattern, URLResolver
from jumpserver.urls import api_v1
path_uuid_pattern = re.compile(r'<\w+:\w+>', re.IGNORECASE)
uuid_pattern = re.compile(r'\(\(\?P<.*>[^)]+\)/\)\?', re.IGNORECASE)
uuid2_pattern = re.compile(r'\(\?P<.*>\[\/\.\]\+\)', re.IGNORECASE)
uuid3_pattern = re.compile(r'\(\?P<.*>\[/\.]\+\)')
def list_urls(patterns, path=None):
""" recursive """
if not path:
path = []
result = []
for pattern in patterns:
if isinstance(pattern, URLPattern):
result.append(''.join(path) + str(pattern.pattern))
elif isinstance(pattern, URLResolver):
result += list_urls(pattern.url_patterns, path + [str(pattern.pattern)])
return result
def parse_to_url(url):
uid = '00000000-0000-0000-0000-000000000000'
url = url.replace('^', '')
url = url.replace('?$', '')
url = url.replace('(?P<format>[a-z0-9]+)', '')
url = url.replace('((?P<terminal>[/.]{36})/)?', uid + '/')
url = url.replace('(?P<pk>[/.]+)', uid)
url = url.replace('\.', '')
url = url.replace('//', '/')
url = url.strip('$')
url = re.sub(path_uuid_pattern, uid, url)
url = re.sub(uuid2_pattern, uid, url)
url = re.sub(uuid_pattern, uid + '/', url)
url = re.sub(uuid3_pattern, uid, url)
url = url.replace('(00000000-0000-0000-0000-000000000000/)?', uid + '/')
return url
def get_api_urls():
urls = []
api_urls = list_urls(api_v1)
for ourl in api_urls:
url = parse_to_url(ourl)
if 'render-to-json' in url:
continue
url = '/api/v1/' + url
urls.append((url, ourl))
return set(urls)
known_unauth_urls = [
"/api/v1/authentication/passkeys/auth/",
"/api/v1/prometheus/metrics/",
"/api/v1/authentication/auth/",
"/api/v1/settings/logo/",
"/api/v1/settings/public/open/",
"/api/v1/authentication/passkeys/login/",
"/api/v1/authentication/tokens/",
"/api/v1/authentication/mfa/challenge/",
"/api/v1/authentication/password/reset-code/",
"/api/v1/authentication/login-confirm-ticket/status/",
"/api/v1/authentication/mfa/select/",
"/api/v1/authentication/mfa/send-code/",
"/api/v1/authentication/sso/login/"
]
known_error_urls = [
'/api/v1/terminal/terminals/00000000-0000-0000-0000-000000000000/sessions/00000000-0000-0000-0000-000000000000/replay/download/',
'/api/v1/terminal/sessions/00000000-0000-0000-0000-000000000000/replay/download/',
]
errors = {}
class Command(BaseCommand):
help = 'Check api if unauthorized'
def handle(self, *args, **options):
settings.LOG_LEVEL = 'ERROR'
urls = get_api_urls()
client = Client()
unauth_urls = []
error_urls = []
unformat_urls = []
for url, ourl in urls:
if '(' in url or '<' in url:
unformat_urls.append([url, ourl])
continue
try:
response = client.get(url, follow=True)
if response.status_code != 401:
errors[url] = str(response.status_code) + ' ' + str(ourl)
unauth_urls.append(url)
except Exception as e:
errors[url] = str(e)
error_urls.append(url)
print("\nNo auth urls:")
for url in set(unauth_urls) - set(known_unauth_urls):
print('"{}", {}'.format(url, errors.get(url, '')))
print("\nError urls:")
for url in set(error_urls):
print(url, ': ' + errors.get(url))
print("\nUnformat urls:")
for url in unformat_urls:
print(url)

View File

@ -96,6 +96,8 @@ def get_all_test_messages(request):
import textwrap
from ..notifications import Message
from django.shortcuts import HttpResponse
if not request.user.is_superuser:
return HttpResponse('没有权限', status=401)
msgs_cls = Message.get_all_sub_messages()
html_data = '<h3>HTML 格式 </h3>'

View File

@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
#
from django.views.generic import TemplateView
from django.conf import settings
from django.http import HttpResponse
from django.views.generic import TemplateView
from common.views.mixins import PermissionsMixin
from rbac.permissions import RBACPermission
@ -16,6 +17,11 @@ class CeleryTaskLogView(PermissionsMixin, TemplateView):
'GET': 'ops.view_celerytaskexecution'
}
def get(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponse(status=401)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({

View File

@ -4,6 +4,7 @@
from __future__ import unicode_literals
from django.core.cache import cache
from django.http import HttpResponse
from django.shortcuts import redirect, reverse
from django.utils.translation import gettext as _
from django.views.generic.base import TemplateView
@ -79,6 +80,9 @@ class TicketDirectApproveView(TemplateView):
return super().get_context_data(**kwargs)
def get(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponse(status=401)
token = kwargs.get('token')
ticket_info = cache.get(token)
if not ticket_info: