From f27f72fd003108aabcdf24456a252d77461d5305 Mon Sep 17 00:00:00 2001 From: hexiaonan-800 Date: Thu, 29 Oct 2026 10:14:55 +0800 Subject: [PATCH] feat: enable application api key --- apps/application/api/application_api_key.py | 51 ++++- ...7_applicationaccesstoken_enable_api_key.py | 17 ++ .../models/application_access_token.py | 20 +- .../serializers/application_access_token.py | 45 ++++ apps/application/urls.py | 1 + apps/application/views/application_api_key.py | 201 ++++++++++++------ 6 files changed, 246 insertions(+), 89 deletions(-) create mode 100644 apps/application/migrations/0017_applicationaccesstoken_enable_api_key.py diff --git a/apps/application/api/application_api_key.py b/apps/application/api/application_api_key.py index 8e4ee71bc4a..9c766bc82d3 100644 --- a/apps/application/api/application_api_key.py +++ b/apps/application/api/application_api_key.py @@ -1,6 +1,7 @@ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter +from application.serializers.application_access_token import ApiKeyEnableEditSerializer from application.serializers.application_api_key import EditApplicationKeySerializer, ApplicationKeySerializerModel from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer @@ -16,6 +17,11 @@ def get_data(self): return ApplicationKeySerializerModel() +class ApiKeyEnableResult(ResultSerializer): + def get_data(self): + return ApiKeyEnableEditSerializer() + + class ApplicationKeyAPI(APIMixin): @staticmethod def get_parameters(): @@ -24,16 +30,16 @@ def get_parameters(): name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, - location='path', + location="path", required=True, ), OpenApiParameter( name="application_id", description="application ID", type=OpenApiTypes.STR, - location='path', + location="path", required=True, - ) + ), ] @staticmethod @@ -48,14 +54,39 @@ def get_response(): class Operate(APIMixin): @staticmethod def get_parameters(): - return [*ApplicationKeyAPI.get_parameters(), OpenApiParameter( - name="api_key_id", - description="ApiKeyId", - type=OpenApiTypes.STR, - location='path', - required=True, - )] + return [ + *ApplicationKeyAPI.get_parameters(), + OpenApiParameter( + name="api_key_id", + description="ApiKeyId", + type=OpenApiTypes.STR, + location="path", + required=True, + ), + ] @staticmethod def get_request(): return EditApplicationKeySerializer + + class Enable(APIMixin): + @staticmethod + def get_parameters(): + return [ + *ApplicationKeyAPI.get_parameters(), + OpenApiParameter( + name="enable_api_key", + description="是否启用 api key 访问应用", + type=OpenApiTypes.BOOL, + location="query", + required=True, + ), + ] + + @staticmethod + def get_request(): + return None + + @staticmethod + def get_response(): + return ApiKeyEnableResult diff --git a/apps/application/migrations/0017_applicationaccesstoken_enable_api_key.py b/apps/application/migrations/0017_applicationaccesstoken_enable_api_key.py new file mode 100644 index 00000000000..bc3e50286fe --- /dev/null +++ b/apps/application/migrations/0017_applicationaccesstoken_enable_api_key.py @@ -0,0 +1,17 @@ +# Generated by Django 6.1.2 on 2026-10-28 10:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("application", "0016_v3_upgrade"), + ] + + operations = [ + migrations.AddField( + model_name="applicationaccesstoken", + name="enable_api_key", + field=models.BooleanField(default=False, verbose_name="是否启用api key访问应用"), + ), + ] diff --git a/apps/application/models/application_access_token.py b/apps/application/models/application_access_token.py index bbad58f1ef2..bed45c29476 100644 --- a/apps/application/models/application_access_token.py +++ b/apps/application/models/application_access_token.py @@ -1,11 +1,12 @@ # coding=utf-8 """ - @project: MaxKB - @Author:虎虎 - @file: application_access_token.py - @date:2025/5/27 9:55 - @desc: +@project: MaxKB +@Author:虎虎 +@file: application_access_token.py +@date:2025/5/27 9:55 +@desc: """ + from django.contrib.postgres.fields import ArrayField from django.db import models @@ -17,19 +18,20 @@ class ApplicationAccessToken(AppModelMixin): """ 应用认证token """ + application = models.OneToOneField(Application, primary_key=True, on_delete=models.CASCADE, verbose_name="应用id") access_token = models.CharField(max_length=128, verbose_name="用户公开访问 认证token", unique=True) is_active = models.BooleanField(default=True, verbose_name="是否开启公开访问") access_num = models.IntegerField(default=100, verbose_name="访问次数") white_active = models.BooleanField(default=False, verbose_name="是否开启白名单") - white_list = ArrayField(verbose_name="白名单列表", - base_field=models.CharField(max_length=128, blank=True) - , default=list) + white_list = ArrayField( + verbose_name="白名单列表", base_field=models.CharField(max_length=128, blank=True), default=list + ) show_source = models.BooleanField(default=False, verbose_name="是否显示知识来源") show_exec = models.BooleanField(default=False, verbose_name="是否显示执行详情") authentication = models.BooleanField(default=False, verbose_name="是否需要认证") authentication_value = models.JSONField(verbose_name="认证的值", default=dict) - + enable_api_key = models.BooleanField(default=False, verbose_name="是否启用api key访问应用") language = models.CharField(max_length=10, verbose_name="语言", default=None, null=True) class Meta: diff --git a/apps/application/serializers/application_access_token.py b/apps/application/serializers/application_access_token.py index cdb3f41a7e2..6f7e03ea2ae 100644 --- a/apps/application/serializers/application_access_token.py +++ b/apps/application/serializers/application_access_token.py @@ -33,6 +33,7 @@ class AccessTokenEditSerializer(serializers.Serializer): show_source = serializers.BooleanField(required=False, label=_("Whether to display knowledge sources")) show_exec = serializers.BooleanField(required=False, label=_("Display execution details")) language = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("language")) + enable_api_key = serializers.BooleanField(required=False, label=_("Enable API key")) authentication = serializers.BooleanField(default=False, label="Do you need authentication") authentication_value = serializers.JSONField(required=False, label="Certified value", default=dict) @@ -73,6 +74,8 @@ def edit(self, instance): application_access_token.language = instance.get("language") if "language" not in instance or instance.get("language") is None: application_access_token.language = None + if "enable_api_key" in instance and instance.get("enable_api_key") is not None: + application_access_token.enable_api_key = instance.get("enable_api_key") if "authentication" in instance and instance.get("authentication") is not None: application_access_token.authentication = instance.get("authentication") application_access_token.authentication_value = { @@ -128,5 +131,47 @@ def one(self, with_valid=True): "show_source": application_access_token.show_source, "show_exec": application_access_token.show_exec, "language": application_access_token.language, + "enable_api_key": application_access_token.enable_api_key, **other, } + + +class ApiKeyEnableEditSerializer(serializers.Serializer): + application_id = serializers.UUIDField(required=True, label=_("Application ID")) + enable_api_key = serializers.BooleanField(required=True, label=_("Enable API key")) + + +class ApiKeyEnableSerializer(serializers.Serializer): + workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) + application_id = serializers.UUIDField(required=True, label=_("Application ID")) + + def is_valid(self, *, raise_exception=False): + super().is_valid(raise_exception=True) + workspace_id = self.data.get("workspace_id") + query_set = QuerySet(Application).filter(id=self.data.get("application_id")) + if workspace_id: + query_set = query_set.filter(workspace_id=workspace_id) + if not query_set.exists(): + raise AppApiException(500, _("Application id does not exist")) + + def edit(self, instance): + self.is_valid(raise_exception=True) + enable_api_key_serializer = ApiKeyEnableEditSerializer( + data={**instance, "application_id": self.data.get("application_id")} + ) + enable_api_key_serializer.is_valid(raise_exception=True) + application_access_token = ( + QuerySet(ApplicationAccessToken).filter(application_id=self.data.get("application_id")).first() + ) + if application_access_token is None: + application_access_token = ApplicationAccessToken( + application_id=self.data.get("application_id"), + access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24], + is_active=True, + ) + application_access_token.enable_api_key = enable_api_key_serializer.validated_data.get("enable_api_key") + application_access_token.save() + return { + "application_id": application_access_token.application_id, + "enable_api_key": application_access_token.enable_api_key, + } diff --git a/apps/application/urls.py b/apps/application/urls.py index e3f4da2ff6d..3269abae289 100644 --- a/apps/application/urls.py +++ b/apps/application/urls.py @@ -30,6 +30,7 @@ path('workspace//application//chat_record/aggregation', views.ApplicationStats.ChatRecordAggregation.as_view()), path('workspace//application//application_key/', views.ApplicationKey.Operate.as_view()), path('workspace//application//application_key//', views.ApplicationKey.Page.as_view()), + path('workspace//application//enable_api_key', views.ApplicationApiKeyEnable.as_view()), path('workspace//application//export', views.ApplicationAPI.Export.as_view()), path('workspace//application//application_version', views.ApplicationVersionView.as_view()), path('workspace//application//access_token', views.AccessToken.as_view()), diff --git a/apps/application/views/application_api_key.py b/apps/application/views/application_api_key.py index bd07d07a44f..ed0f858c30f 100644 --- a/apps/application/views/application_api_key.py +++ b/apps/application/views/application_api_key.py @@ -7,6 +7,7 @@ from application.api.application_api_key import ApplicationKeyAPI from application.models import Application from application.serializers.application_api_key import ApplicationKeySerializer +from application.serializers.application_access_token import ApiKeyEnableSerializer from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.auth.constants.compare_constants import CompareConstants @@ -21,9 +22,7 @@ def get_application_operation_object(application_id): application_model = QuerySet(model=Application).filter(id=application_id).first() if application_model is not None: - return { - "name": application_model.name - } + return {"name": application_model.name} return {} @@ -31,105 +30,167 @@ class ApplicationKey(APIView): authentication_classes = [TokenAuth] @extend_schema( - methods=['POST'], - description=_('Create application ApiKey'), - summary=_('Create application ApiKey'), - operation_id=_('Create application ApiKey'), # type: ignore + methods=["POST"], + description=_("Create application ApiKey"), + summary=_("Create application ApiKey"), + operation_id=_("Create application ApiKey"), # type: ignore parameters=ApplicationKeyAPI.get_parameters(), request=None, responses=ApplicationKeyAPI.get_response(), - tags=[_('Application Api Key')] # type: ignore + tags=[_("Application Api Key")], # type: ignore + ) + @log( + menu="Application", + operate="Add ApiKey", + get_operation_object=lambda r, k: get_application_operation_object(k.get("application_id")), + ) + @has_permissions( + PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), + PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.APPLICATION.get_workspace_application_permission()], + compare=CompareConstants.AND, + ), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) - @log(menu='Application', operate="Add ApiKey", - get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), - ) - @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), - PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), - ViewPermission([RoleConstants.USER.get_workspace_role()], - [ - PermissionConstants.APPLICATION.get_workspace_application_permission()], - compare=CompareConstants.AND), - RoleConstants.WORKSPACE_MANAGE.get_workspace_role() - ) def post(self, request: Request, workspace_id: str, application_id: str): - return result.success(ApplicationKeySerializer( - data={'application_id': application_id, - 'workspace_id': workspace_id}).generate()) + return result.success( + ApplicationKeySerializer(data={"application_id": application_id, "workspace_id": workspace_id}).generate() + ) class Page(APIView): authentication_classes = [TokenAuth] @extend_schema( - methods=['GET'], - description=_('GET application ApiKey List'), - summary=_('Create application ApiKey List'), - operation_id=_('Create application ApiKey List'), # type: ignore + methods=["GET"], + description=_("GET application ApiKey List"), + summary=_("Create application ApiKey List"), + operation_id=_("Create application ApiKey List"), # type: ignore parameters=ApplicationKeyAPI.get_parameters(), responses=ApplicationKeyAPI.List.get_response(), - tags=[_('Application Api Key')] # type: ignore + tags=[_("Application Api Key")], # type: ignore + ) + @has_permissions( + PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), + PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.APPLICATION.get_workspace_application_permission()], + compare=CompareConstants.AND, + ), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), ) - @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), - PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), - ViewPermission([RoleConstants.USER.get_workspace_role()], - [PermissionConstants.APPLICATION.get_workspace_application_permission()], - compare=CompareConstants.AND), - RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) def get(self, request: Request, workspace_id: str, application_id: str, current_page: int, page_size: int): - return result.success(ApplicationKeySerializer( - data={'application_id': application_id, - 'workspace_id': workspace_id, - 'order_by': request.query_params.get('order_by')}).page(current_page, page_size)) + return result.success( + ApplicationKeySerializer( + data={ + "application_id": application_id, + "workspace_id": workspace_id, + "order_by": request.query_params.get("order_by"), + } + ).page(current_page, page_size) + ) class Operate(APIView): authentication_classes = [TokenAuth] @extend_schema( - methods=['PUT'], - description=_('Modify application API_KEY'), - summary=_('Modify application API_KEY'), - operation_id=_('Modify application API_KEY'), # type: ignore + methods=["PUT"], + description=_("Modify application API_KEY"), + summary=_("Modify application API_KEY"), + operation_id=_("Modify application API_KEY"), # type: ignore parameters=ApplicationKeyAPI.Operate.get_parameters(), request=ApplicationKeyAPI.Operate.get_request(), responses=DefaultResultSerializer, - tags=[_('Application Api Key')] # type: ignore + tags=[_("Application Api Key")], # type: ignore + ) + @has_permissions( + PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), + PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.APPLICATION.get_workspace_application_permission()], + compare=CompareConstants.AND, + ), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ) + @log( + menu="Application", + operate="Modify application API_KEY", + get_operation_object=lambda r, k: get_application_operation_object(k.get("application_id")), ) - @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), - PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), - ViewPermission([RoleConstants.USER.get_workspace_role()], - [PermissionConstants.APPLICATION.get_workspace_application_permission()], - compare=CompareConstants.AND), - RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) - @log(menu='Application', operate="Modify application API_KEY", - get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), - ) def put(self, request: Request, workspace_id: str, application_id: str, api_key_id: str): return result.success( ApplicationKeySerializer.Operate( - data={'workspace_id': workspace_id, 'application_id': application_id, - 'api_key_id': api_key_id}).edit( - request.data)) + data={"workspace_id": workspace_id, "application_id": application_id, "api_key_id": api_key_id} + ).edit(request.data) + ) @extend_schema( - methods=['DELETE'], - description=_('Delete Application API_KEY'), - summary=_('Delete Application API_KEY'), - operation_id=_('Delete Application API_KEY'), # type: ignore + methods=["DELETE"], + description=_("Delete Application API_KEY"), + summary=_("Delete Application API_KEY"), + operation_id=_("Delete Application API_KEY"), # type: ignore parameters=ApplicationKeyAPI.Operate.get_parameters(), request=ApplicationKeyAPI.Operate.get_request(), responses=DefaultResultSerializer, - tags=[_('Application Api Key')] # type: ignore + tags=[_("Application Api Key")], # type: ignore + ) + @has_permissions( + PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), + PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.APPLICATION.get_workspace_application_permission()], + compare=CompareConstants.AND, + ), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ) + @log( + menu="Application", + operate="Delete application API_KEY", + get_operation_object=lambda r, k: get_application_operation_object(k.get("application_id")), ) - @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), - PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), - ViewPermission([RoleConstants.USER.get_workspace_role()], - [PermissionConstants.APPLICATION.get_workspace_application_permission()], - compare=CompareConstants.AND), - RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) - @log(menu='Application', operate="Delete application API_KEY", - get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), - ) def delete(self, request: Request, workspace_id: str, application_id: str, api_key_id: str): return result.success( ApplicationKeySerializer.Operate( - data={'workspace_id': workspace_id, 'application_id': application_id, - 'api_key_id': api_key_id}).delete()) + data={"workspace_id": workspace_id, "application_id": application_id, "api_key_id": api_key_id} + ).delete() + ) + + +class ApplicationApiKeyEnable(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=["PUT"], + description=_("Enable or disable application API key access"), + summary=_("Enable or disable application API key access"), + operation_id=_("Enable or disable application API key access"), # type: ignore + parameters=ApplicationKeyAPI.Enable.get_parameters(), + request=None, + responses=ApplicationKeyAPI.Enable.get_response(), + tags=[_("Application Api Key")], # type: ignore + ) + @log( + menu="Application", + operate="Enable/Disable application API Key", + get_operation_object=lambda r, k: get_application_operation_object(k.get("application_id")), + ) + @has_permissions( + PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(), + PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.APPLICATION.get_workspace_application_permission()], + compare=CompareConstants.AND, + ), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ) + def put(self, request: Request, workspace_id: str, application_id: str): + return result.success( + ApiKeyEnableSerializer(data={"workspace_id": workspace_id, "application_id": application_id}).edit( + request.query_params.dict() + ) + )