mirror of https://github.com/jumpserver/jumpserver
Merge pull request #11552 from jumpserver/pr@dev@add_api_check_for_unauth
perf: 添加 check api,检测所有 apipull/11553/head
commit
7c973616cd
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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>'
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue