fix(attachment-url): only allow file collections with public URL access (#6664)

* refactor: isPublicAccessStorage

* fix: bug

* fix: bug

* fix: only allow file collections with public URL access

* fix: build

* fix: test
This commit is contained in:
Katherine 2025-04-14 22:35:17 +08:00 committed by GitHub
parent 3e2abe3d3c
commit f99cdb5f02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 77 additions and 33 deletions

View File

@ -54,7 +54,7 @@ describe('CollectionSelect', () => {
role="button" role="button"
> >
<div <div
class="css-9mlexe ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz" class="css-a7w9kk ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz"
> >
<div <div
class="ant-formily-item-label" class="ant-formily-item-label"
@ -195,7 +195,7 @@ describe('CollectionSelect', () => {
role="button" role="button"
> >
<div <div
class="css-9mlexe ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz" class="css-a7w9kk ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz"
> >
<div <div
class="ant-formily-item-label" class="ant-formily-item-label"

View File

@ -48,6 +48,7 @@ export type RemoteSelectProps<P = any> = SelectProps<P, any> & {
CustomDropdownRender?: (v: any) => any; CustomDropdownRender?: (v: any) => any;
optionFilter?: (option: any) => boolean; optionFilter?: (option: any) => boolean;
toOptionsItem?: (data) => any; toOptionsItem?: (data) => any;
onSuccess?: (data) => any;
}; };
const InternalRemoteSelect = withDynamicSchemaProps( const InternalRemoteSelect = withDynamicSchemaProps(
@ -68,6 +69,7 @@ const InternalRemoteSelect = withDynamicSchemaProps(
dataSource: propsDataSource, dataSource: propsDataSource,
toOptionsItem = (value) => value, toOptionsItem = (value) => value,
popupMatchSelectWidth = false, popupMatchSelectWidth = false,
onSuccess,
...others ...others
} = props; } = props;
const dataSource = useDataSourceKey(); const dataSource = useDataSourceKey();
@ -178,6 +180,7 @@ const InternalRemoteSelect = withDynamicSchemaProps(
{ {
manual, manual,
debounceWait: wait, debounceWait: wait,
onSuccess,
...(service.defaultParams ? { defaultParams: [service.defaultParams] } : {}), ...(service.defaultParams ? { defaultParams: [service.defaultParams] } : {}),
}, },
); );

View File

@ -113,7 +113,6 @@ const InnerAttachmentUrl = (props) => {
if (underFilter) { if (underFilter) {
return <Input {...props} />; return <Input {...props} />;
} }
console.log(collectionField);
return ( return (
<div style={{ width: '100%', overflow: 'auto' }}> <div style={{ width: '100%', overflow: 'auto' }}>
<AssociationField.FileSelector <AssociationField.FileSelector

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import { useFieldSchema } from '@formily/react'; import { useFieldSchema, useField } from '@formily/react';
import { useCollectionField, useDesignable, useRequest } from '@nocobase/client'; import { useCollectionField, useDesignable, useRequest } from '@nocobase/client';
import { cloneDeep, uniqBy } from 'lodash'; import { cloneDeep, uniqBy } from 'lodash';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -63,15 +63,11 @@ export const useInsertSchema = (component) => {
export const useAttachmentTargetProps = () => { export const useAttachmentTargetProps = () => {
const { t } = useTranslation(); const { t } = useTranslation();
// TODO(refactor): whitelist should be changed to storage propertyurl is signed by plugin-s3-pro, this enmus is from plugin-file-manager const field = useField();
const buildInStorage = ['local', 'ali-oss', 's3', 'tx-cos'];
return { return {
service: { service: {
resource: 'collections', resource: 'collections:listFileCollectionsWithPublicStorage',
params: { params: {
filter: {
'options.template': 'file',
},
paginate: false, paginate: false,
}, },
}, },
@ -80,27 +76,9 @@ export const useAttachmentTargetProps = () => {
label: 'title', label: 'title',
value: 'name', value: 'name',
}, },
mapOptions: (value) => { onSuccess: (data) => {
if (value.name === 'attachments') { field.data = field.data || {};
return { field.data.options = data?.data;
...value,
title: t('Attachments'),
};
}
return value;
},
toOptionsItem: (data) => {
data.unshift({
name: 'attachments',
title: t('Attachments'),
});
return uniqBy(
data.filter((v) => v.name),
'name',
);
},
optionFilter: (option) => {
return !option.storage || buildInStorage.includes(option.storage);
}, },
}; };
}; };

View File

@ -51,12 +51,18 @@ export class AttachmentURLFieldInterface extends CollectionFieldInterface {
...defaultProps, ...defaultProps,
target: { target: {
required: true, required: true,
default: 'attachments',
type: 'string', type: 'string',
title: tStr('Which file collection should it be uploaded to'), title: tStr('Which file collection should it be uploaded to'),
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'RemoteSelect', 'x-component': 'RemoteSelect',
'x-use-component-props': useAttachmentTargetProps, 'x-use-component-props': useAttachmentTargetProps,
'x-reactions': (field) => {
const options = field.data?.options || [];
const hasAttachments = options.some((opt) => opt?.name === 'attachments');
if (hasAttachments) {
!field.initialValue && field.setInitialValue('attachments');
}
},
}, },
targetKey: { targetKey: {
'x-hidden': true, 'x-hidden': true,

View File

@ -8,13 +8,50 @@
*/ */
import { Plugin } from '@nocobase/server'; import { Plugin } from '@nocobase/server';
import PluginFileManagerServer from '@nocobase/plugin-file-manager';
export class PluginFieldAttachmentUrlServer extends Plugin { export class PluginFieldAttachmentUrlServer extends Plugin {
async afterAdd() {} async afterAdd() {}
async beforeLoad() {} async beforeLoad() {}
async load() {} async load() {
this.app.resourceManager.registerActionHandlers({
'collections:listFileCollectionsWithPublicStorage': async (ctx, next) => {
const fileCollections = await this.db.getRepository('collections').find({
filter: {
'options.template': 'file',
},
});
const filePlugin = this.pm.get('file-manager') as PluginFileManagerServer | any;
const options = [];
const fileCollection = this.db.getCollection('attachments');
if (await filePlugin.isPublicAccessStorage(fileCollection?.options?.storage)) {
options.push({
title: '{{t("Attachments")}}',
name: 'attachments',
});
}
for (const fileCollection of fileCollections) {
if (await filePlugin.isPublicAccessStorage(fileCollection?.options?.storage)) {
options.push({
name: fileCollection.name,
title: fileCollection.title,
});
}
}
ctx.body = options;
await next();
},
});
}
async install() {} async install() {}

View File

@ -313,6 +313,27 @@ export class PluginFileManagerServer extends Plugin {
const storageType = this.storageTypes.get(storage.type); const storageType = this.storageTypes.get(storage.type);
return new storageType(storage).getFileURL(file, preview ? storage.options.thumbnailRule : ''); return new storageType(storage).getFileURL(file, preview ? storage.options.thumbnailRule : '');
} }
async isPublicAccessStorage(storageName) {
const storageRepository = this.db.getRepository('storages');
const storages = await storageRepository.findOne({
filter: { default: true },
});
let storage;
if (!storageName) {
storage = storages;
} else {
storage = await storageRepository.findOne({
filter: {
name: storageName,
},
});
}
storage = this.parseStorage(storage);
if (['local', 'ali-oss', 's3', 'tx-cos'].includes(storage.type)) {
return true;
}
return !!storage.options?.public;
}
} }
export default PluginFileManagerServer; export default PluginFileManagerServer;