From 532d4d24ae4b0640a2ecf868897dce2408caca15 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Wed, 6 Sep 2023 09:15:37 +0800 Subject: [PATCH] add views --- apps/api/views.py | 4 +- apps/openapi/models.py | 4 ++ apps/openapi/urls.py | 9 ++++ apps/openapi/utils.py | 26 +++++++++++ apps/openapi/views.py | 48 +++++++++++++++++++- apps/proxy/models.py | 8 ++++ apps/urls.py | 32 +++++++++++-- apps/utils.py | 6 ++- wait-for-it.sh | 100 ++++++++++++++++++++--------------------- 9 files changed, 178 insertions(+), 59 deletions(-) create mode 100644 apps/openapi/urls.py create mode 100644 apps/openapi/utils.py diff --git a/apps/api/views.py b/apps/api/views.py index caae47e0dd..079aa8c12d 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -18,7 +18,7 @@ gen_datetime_list, get_client_ip, get_current_datetime, - handle_json_post, + handle_json_request, traffic_format, ) @@ -133,7 +133,7 @@ def get(self, request, node_id): JsonResponse(node.get_proxy_configs()) if node else HttpResponseBadRequest() ) - @method_decorator(handle_json_post) + @method_decorator(handle_json_request) @method_decorator(api_authorized) def post(self, request, node_id): node = m.ProxyNode.get_or_none(node_id) diff --git a/apps/openapi/models.py b/apps/openapi/models.py index 2353b7adc7..97439ac7f4 100644 --- a/apps/openapi/models.py +++ b/apps/openapi/models.py @@ -18,3 +18,7 @@ class UserOpenAPIKey(BaseModel): class Meta: verbose_name = "用户秘钥" verbose_name_plural = "用户秘钥" + + @classmethod + def get_by_key(cls, key: str): + return cls.objects.filter(key=key).first() diff --git a/apps/openapi/urls.py b/apps/openapi/urls.py new file mode 100644 index 0000000000..02531ecb2c --- /dev/null +++ b/apps/openapi/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +app_name = "openapi" +urlpatterns = [ + path("proxy_nodes/search/", views.ProxyNodeSearchView.as_view()), + path("proxy_nodes//", views.ProxyNodeDetailView.as_view()), +] diff --git a/apps/openapi/utils.py b/apps/openapi/utils.py new file mode 100644 index 0000000000..693e7adfeb --- /dev/null +++ b/apps/openapi/utils.py @@ -0,0 +1,26 @@ +from functools import wraps + +from django.http import JsonResponse + +from apps.openapi.models import UserOpenAPIKey + + +def gen_common_error_response(msg: str, status=400) -> JsonResponse: + return JsonResponse({"error_msg": msg}, status=status) + + +def openapi_authorized(view_func): + @wraps(view_func) + def wrapper(request, *args, **kwargs): + key = request.META.get("HTTP_X_API_KEY", "") + if not key: + return gen_common_error_response( + "x-api-key in header not found", status=401 + ) + user_key = UserOpenAPIKey.get_by_key(key) + if not user_key: + return gen_common_error_response("x-api-key is invalid", status=401) + request.user = user_key.user + return view_func(request, *args, **kwargs) + + return wrapper diff --git a/apps/openapi/views.py b/apps/openapi/views.py index 60f00ef0ef..065a998054 100644 --- a/apps/openapi/views.py +++ b/apps/openapi/views.py @@ -1 +1,47 @@ -# Create your views here. +from django.forms import model_to_dict +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views import View +from django.views.decorators.csrf import csrf_exempt + +from apps.openapi.utils import gen_common_error_response, openapi_authorized +from apps.proxy.models import ProxyNode +from apps.utils import handle_json_request + + +class ProxyNodeSearchView(View): + @csrf_exempt + @method_decorator(openapi_authorized) + def dispatch(self, *args, **kwargs): + return super(ProxyNodeSearchView, self).dispatch(*args, **kwargs) + + def get(self, request): + ip = request.GET.get("ip") + if not ip: + return gen_common_error_response("ip in query is required") + + node = ProxyNode.get_by_ip(ip) + if not node: + return gen_common_error_response( + f"node with ip:{ip} not found", status=404 + ) + return JsonResponse(model_to_dict(node)) + + +class ProxyNodeDetailView(View): + @csrf_exempt + @method_decorator(openapi_authorized) + @method_decorator(handle_json_request) + def dispatch(self, *args, **kwargs): + return super(ProxyNodeDetailView, self).dispatch(*args, **kwargs) + + def patch(self, request, node_id): + node = ProxyNode.get_by_id(node_id) + if not node: + return gen_common_error_response( + f"node with id:{node_id} not found", status=404 + ) + enable = request.json.get("enable") + node.enable = enable + node.save() + return JsonResponse(model_to_dict(node)) diff --git a/apps/proxy/models.py b/apps/proxy/models.py index 4d261d7d52..204b0cfc82 100644 --- a/apps/proxy/models.py +++ b/apps/proxy/models.py @@ -228,6 +228,10 @@ def __str__(self) -> str: def get_by_id_with_cache(cls, id): return cls.objects.get(id=id) + @classmethod + def get_by_id(cls, id): + return cls.objects.filter(id=id).first() + @classmethod def get_active_nodes(cls, level=None): query = cls.objects.filter(enable=True) @@ -245,6 +249,10 @@ def calc_total_traffic(cls): used_traffic = aggs["used_traffic"] or 0 return utils.traffic_format(used_traffic) + @classmethod + def get_by_ip(clc, ip: str): + return clc.objects.filter(server=ip).first() + def get_trojan_node_config(self): xray_config = XRayTemplates.gen_base_config( self.xray_grpc_port, diff --git a/apps/urls.py b/apps/urls.py index 5037cb5928..da1734029d 100644 --- a/apps/urls.py +++ b/apps/urls.py @@ -1,3 +1,4 @@ +import debug_toolbar from django.conf import settings from django.contrib import admin from django.urls import include, path @@ -5,20 +6,43 @@ from apps.custom_views import AsyncPasswordResetView urlpatterns = [ - path("", include("apps.sspanel.urls", namespace="sspanel")), - path("api/", include("apps.api.urls", namespace="api")), path( "accounts/password_reset/", AsyncPasswordResetView.as_view(), name="password_reset", ), # NOTE 重写了重置密码的逻辑 一定要在`django.contrib.auth.urls`之前注册,不然会被覆盖 path("accounts/", include("django.contrib.auth.urls")), +] + +# append sspanel template urls +urlpatterns.append( + path("", include("apps.sspanel.urls", namespace="sspanel")), +) + +# append proxy api urls +urlpatterns.append( + path("api/", include("apps.api.urls", namespace="api")), +) + +# append admin urls +urlpatterns.append( path("admin/", admin.site.urls, name="admin"), +) + +# append prometheus urls +urlpatterns.append( path("prom/", include("django_prometheus.urls")), -] +) + + +# append openapi urls +urlpatterns.append( + path("openapi/v1/", include("apps.openapi.urls", namespace="openapi")) +) + +# append django debug toolbar urls if settings.DEBUG is True: - import debug_toolbar urlpatterns.append(path("__debug__/", include(debug_toolbar.urls))) diff --git a/apps/utils.py b/apps/utils.py index 8d367f46ad..0e7a4c6160 100644 --- a/apps/utils.py +++ b/apps/utils.py @@ -78,11 +78,13 @@ def wrapper(request, *args, **kwargs): return wrapper -def handle_json_post(view_func): +def handle_json_request(view_func): @wraps(view_func) def wrapper(request, *args, **kw): - if request.method == "POST": + try: request.json = json.loads(request.body) + except Exception: + return JsonResponse({"msg": "bad request"}, status=400) return view_func(request, *args, **kw) return wrapper diff --git a/wait-for-it.sh b/wait-for-it.sh index 7f67086abc..03ccab7b95 100755 --- a/wait-for-it.sh +++ b/wait-for-it.sh @@ -4,11 +4,11 @@ TIMEOUT=15 QUIET=0 echoerr() { - if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi + if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi } usage() { - exitcode="$1" + exitcode="$1" cat << USAGE >&2 Usage: $cmdname host:port [-t timeout] [-- command args] @@ -16,64 +16,64 @@ Usage: -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes USAGE - exit "$exitcode" + exit "$exitcode" } wait_for() { - for i in `seq $TIMEOUT` ; do - nc -z "$rHOST" "$rPORT" > /dev/null 2>&1 - - result=$? - if [ $result -eq 0 ] ; then - if [ $# -gt 0 ] ; then - exec "$@" - fi - exit 0 - fi - sleep 1 - done - echo "Operation timed out" >&2 - exit 1 + for i in `seq $TIMEOUT` ; do + nc -z "$rHOST" "$rPORT" > /dev/null 2>&1 + + result=$? + if [ $result -eq 0 ] ; then + if [ $# -gt 0 ] ; then + exec "$@" + fi + exit 0 + fi + sleep 1 + done + echo "Operation timed out" >&2 + exit 1 } while [ $# -gt 0 ] do - case "$1" in - *:* ) - rHOST=$(printf "%s\n" "$1"| cut -d : -f 1) - rPORT=$(printf "%s\n" "$1"| cut -d : -f 2) - shift 1 - ;; - -q | --quiet) - QUIET=1 - shift 1 - ;; - -t) - TIMEOUT="$2" - if [ "$TIMEOUT" = "" ]; then break; fi - shift 2 - ;; - --timeout=*) - TIMEOUT="${1#*=}" - shift 1 - ;; - --) - shift - break - ;; - --help) - usage 0 - ;; - *) - echoerr "Unknown argument: $1" - usage 1 - ;; - esac + case "$1" in + *:* ) + rHOST=$(printf "%s\n" "$1"| cut -d : -f 1) + rPORT=$(printf "%s\n" "$1"| cut -d : -f 2) + shift 1 + ;; + -q | --quiet) + QUIET=1 + shift 1 + ;; + -t) + TIMEOUT="$2" + if [ "$TIMEOUT" = "" ]; then break; fi + shift 2 + ;; + --timeout=*) + TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + break + ;; + --help) + usage 0 + ;; + *) + echoerr "Unknown argument: $1" + usage 1 + ;; + esac done if [ "$rHOST" = "" -o "$rPORT" = "" ]; then - echoerr "Error: you need to provide a host and port to test." - usage 2 + echoerr "Error: you need to provide a host and port to test." + usage 2 fi wait_for "$@"