Skip to content

Commit

Permalink
feat: 跨域设置(#276)
Browse files Browse the repository at this point in the history
  • Loading branch information
shaohuzhang1 committed May 8, 2024
1 parent d4e742f commit 267be44
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.1.13 on 2024-05-08 13:57

import django.contrib.postgres.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('application', '0005_alter_chat_abstract_alter_chatrecord_answer_text'),
]

operations = [
migrations.AddField(
model_name='applicationapikey',
name='allow_cross_domain',
field=models.BooleanField(default=False, verbose_name='是否允许跨域'),
),
migrations.AddField(
model_name='applicationapikey',
name='cross_domain_list',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='跨域列表'),
),
]
4 changes: 4 additions & 0 deletions apps/application/models/api_key_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class ApplicationApiKey(AppModelMixin):
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id")
application = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name="应用id")
is_active = models.BooleanField(default=True, verbose_name="是否开启")
allow_cross_domain = models.BooleanField(default=False, verbose_name="是否允许跨域")
cross_domain_list = ArrayField(verbose_name="跨域列表",
base_field=models.CharField(max_length=128, blank=True)
, default=list)

class Meta:
db_table = "application_api_key"
Expand Down
32 changes: 21 additions & 11 deletions apps/application/serializers/application_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
from embedding.models import SearchMode
from setting.models import AuthOperate
from setting.models.model_management import Model
from setting.models_provider.constants.model_provider_constants import ModelProvideConstants
from setting.serializers.provider_serializers import ModelSerializer
from smartdoc.conf import PROJECT_DIR

Expand Down Expand Up @@ -583,6 +582,15 @@ def list(self, with_valid=True):
class Edit(serializers.Serializer):
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean("是否可用"))

allow_cross_domain = serializers.BooleanField(required=False,
error_messages=ErrMessage.boolean("是否允许跨域"))

cross_domain_list = serializers.ListSerializer(required=False,
child=serializers.CharField(required=True,
error_messages=ErrMessage.char(
"跨域列表")),
error_messages=ErrMessage.char("跨域地址"))

class Operate(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))

Expand All @@ -599,15 +607,17 @@ def delete(self, with_valid=True):
def edit(self, instance, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
ApplicationSerializer.Edit(data=instance).is_valid(raise_exception=True)

ApplicationSerializer.ApplicationKeySerializer.Edit(data=instance).is_valid(raise_exception=True)
api_key_id = self.data.get("api_key_id")
application_id = self.data.get('application_id')
application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id,
application_id=application_id).first()
if application_api_key is None:
raise AppApiException(500, '不存在')
if 'is_active' in instance and instance.get('is_active') is not None:
api_key_id = self.data.get("api_key_id")
application_id = self.data.get('application_id')
application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id,
application_id=application_id).first()
if application_api_key is None:
raise AppApiException(500, '不存在')

application_api_key.is_active = instance.get('is_active')
application_api_key.save()
if 'allow_cross_domain' in instance and instance.get('allow_cross_domain') is not None:
application_api_key.allow_cross_domain = instance.get('allow_cross_domain')
if 'cross_domain_list' in instance and instance.get('cross_domain_list') is not None:
application_api_key.cross_domain_list = instance.get('cross_domain_list')
application_api_key.save()
5 changes: 4 additions & 1 deletion apps/application/swagger_api/application_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ def get_request_body_api():
properties={
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否激活",
description="是否激活"),

'allow_cross_domain': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否允许跨域",
description="是否允许跨域"),
'cross_domain_list': openapi.Schema(type=openapi.TYPE_ARRAY, title='跨域列表',
items=openapi.Schema(type=openapi.TYPE_STRING))
}
)

Expand Down
43 changes: 43 additions & 0 deletions apps/common/middleware/cross_domain_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# coding=utf-8
"""
@project: maxkb
@Author:虎
@file: cross_domain_middleware.py
@date:2024/5/8 13:36
@desc:
"""
from django.db.models import QuerySet
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin

from application.models.api_key_model import ApplicationApiKey


class CrossDomainMiddleware(MiddlewareMixin):

def process_request(self, request):
if request.method == 'OPTIONS':
auth = request.META.get('HTTP_AUTHORIZATION')
if auth is not None and str(auth).startswith("application-"):
application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=auth).first()
if application_api_key.allow_cross_domain:
return HttpResponse(status=200,
headers={
"Access-Control-Allow-Origin": "*" if application_api_key.cross_domain_list is None or len(
application_api_key.cross_domain_list) == 0 else ",".join(
application_api_key.cross_domain_list),
"Access-Control-Allow-Methods": "GET,POST,DELETE,PUT",
"Access-Control-Allow-Headers": "Origin,X-Requested-With,Content-Type,Accept,Authorization,token"})

def process_response(self, request, response):
auth = request.META.get('HTTP_AUTHORIZATION')
if auth is not None and str(auth).startswith("application-"):
application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=auth).first()
if application_api_key.allow_cross_domain:
response['Access-Control-Allow-Origin'] = "*" if application_api_key.cross_domain_list is None or len(
application_api_key.cross_domain_list) == 0 else ",".join(
application_api_key.cross_domain_list)
response['Access-Control-Allow-Methods'] = 'GET,POST,DELETE,PUT'
response[
'Access-Control-Allow-Headers'] = "Origin,X-Requested-With,Content-Type,Accept,Authorization,token"
return response
3 changes: 2 additions & 1 deletion apps/smartdoc/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'common.middleware.static_headers_middleware.StaticHeadersMiddleware'
'common.middleware.static_headers_middleware.StaticHeadersMiddleware',
'common.middleware.cross_domain_middleware.CrossDomainMiddleware'

]

Expand Down
20 changes: 18 additions & 2 deletions ui/src/views/application-overview/component/APIKeyDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
</el-table-column>
<el-table-column label="操作" align="left" width="80">
<template #default="{ row }">
<span class="mr-4">
<el-tooltip effect="dark" content="设置" placement="top">
<el-button type="primary" text @click.stop="settingApiKey(row)">
<el-icon><Setting /></el-icon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" content="删除" placement="top">
<el-button type="primary" text @click="deleteApiKey(row)">
<el-icon><Delete /></el-icon>
Expand All @@ -34,24 +41,25 @@
</template>
</el-table-column>
</el-table>
<SettingAPIKeyDialog ref="SettingAPIKeyDialogRef" @refresh="refresh" />
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { copyClick } from '@/utils/clipboard'
import overviewApi from '@/api/application-overview'
import SettingAPIKeyDialog from './SettingAPIKeyDialog.vue'
import { datetimeFormat } from '@/utils/time'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import useStore from '@/stores'
const route = useRoute()
const {
params: { id }
} = route
const { application } = useStore()
const emit = defineEmits(['addData'])
const SettingAPIKeyDialogRef = ref()
const dialogVisible = ref<boolean>(false)
const loading = ref(false)
const apiKey = ref<any>(null)
Expand All @@ -62,6 +70,10 @@ watch(dialogVisible, (bool) => {
}
})
function settingApiKey(row: any) {
SettingAPIKeyDialogRef.value.open(row)
}
function deleteApiKey(row: any) {
MsgConfirm(
`是否删除API Key:${row.secret_key} ?`,
Expand Down Expand Up @@ -108,6 +120,10 @@ function getApiKeyList() {
})
}
function refresh() {
getApiKeyList()
}
defineExpose({ open })
</script>
<style lang="scss" scope>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<template>
<el-dialog title="设置" v-model="dialogVisible">
<el-form label-position="top" ref="settingFormRef" :model="form">
<el-form-item label="允许跨域地址" @click.prevent>
<el-switch size="small" v-model="form.allow_cross_domain"></el-switch>
</el-form-item>
<el-form-item>
<el-input
v-model="form.cross_domain_list"
placeholder="请输入允许的跨域地址,开启后不输入跨域地址则不限制。
跨域地址一行一个,如:
http://127.0.0.1:5678
https://dataease.io"
:rows="10"
type="textarea"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
<el-button type="primary" @click="submit(settingFormRef)" :loading="loading">
保存
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import type { FormInstance, FormRules } from 'element-plus'
import overviewApi from '@/api/application-overview'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
const route = useRoute()
const {
params: { id }
} = route
const emit = defineEmits(['refresh'])
const settingFormRef = ref()
const form = ref<any>({
allow_cross_domain: false,
cross_domain_list: ''
})
const dialogVisible = ref<boolean>(false)
const loading = ref(false)
const APIKeyId = ref('')
watch(dialogVisible, (bool) => {
if (!bool) {
form.value = {
allow_cross_domain: false,
cross_domain_list: ''
}
}
})
const open = (data: any) => {
APIKeyId.value = data.id
form.value.allow_cross_domain = data.allow_cross_domain
form.value.cross_domain_list = data.cross_domain_list?.length
? data.cross_domain_list?.join('\n')
: ''
dialogVisible.value = true
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
const obj = {
allow_cross_domain: form.value.allow_cross_domain,
cross_domain_list: form.value.cross_domain_list
? form.value.cross_domain_list.split('\n')
: []
}
overviewApi.putAPIKey(id as string, APIKeyId.value, obj, loading).then((res) => {
emit('refresh')
MsgSuccess('设置成功')
dialogVisible.value = false
})
}
})
}
defineExpose({ open })
</script>
<style lang="scss" scope></style>

0 comments on commit 267be44

Please sign in to comment.