mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
fix: Sharepoint file selection correctly applies filter (#23155)
This commit is contained in:
@@ -12,6 +12,8 @@ import { jsonParse, NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||
import type { IErrorResponse } from './interfaces';
|
||||
import { microsoftSharePointApiRequest } from '../transport';
|
||||
|
||||
export const escapeFilterValue = (value: string) => value.replaceAll("'", "''");
|
||||
|
||||
export async function simplifyItemPostReceive(
|
||||
this: IExecuteSingleFunctions,
|
||||
items: INodeExecutionData[],
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import type { IDriveItem, IList, IListItem, ISite } from '../helpers/interfaces';
|
||||
import { escapeFilterValue } from '../helpers/utils';
|
||||
import { microsoftSharePointApiRequest } from '../transport';
|
||||
|
||||
export async function getFiles(
|
||||
@@ -34,7 +35,7 @@ export async function getFiles(
|
||||
$select: 'id,name,file',
|
||||
};
|
||||
if (filter) {
|
||||
qs.$filter = `name eq '${filter}'`;
|
||||
qs.$filter = `startswith(name, '${escapeFilterValue(filter)}')`;
|
||||
}
|
||||
response = await microsoftSharePointApiRequest.call(
|
||||
this,
|
||||
@@ -85,9 +86,6 @@ export async function getFolders(
|
||||
// https://learn.microsoft.com/en-us/onedrive/developer/rest-api/concepts/filtering-results?view=odsp-graph-online#filterable-properties
|
||||
$filter: 'folder ne null',
|
||||
};
|
||||
if (filter) {
|
||||
qs.$filter = `name eq '${filter}'`;
|
||||
}
|
||||
response = await microsoftSharePointApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
@@ -100,7 +98,7 @@ export async function getFolders(
|
||||
const items: IDriveItem[] = response.value;
|
||||
|
||||
const results: INodeListSearchItems[] = items
|
||||
.filter((x) => x.folder)
|
||||
.filter((x) => x.folder && (!filter || x.name?.toLowerCase()?.includes?.(filter.toLowerCase())))
|
||||
.map((g) => ({
|
||||
name: g.name,
|
||||
value: g.id,
|
||||
@@ -137,7 +135,7 @@ export async function getItems(
|
||||
$select: 'id,fields',
|
||||
};
|
||||
if (filter) {
|
||||
qs.$filter = `fields/Title eq '${filter}'`;
|
||||
qs.$filter = `fields/Title eq '${escapeFilterValue(filter)}'`;
|
||||
}
|
||||
response = await microsoftSharePointApiRequest.call(
|
||||
this,
|
||||
@@ -185,7 +183,7 @@ export async function getLists(
|
||||
$select: 'id,displayName',
|
||||
};
|
||||
if (filter) {
|
||||
qs.$filter = `displayName eq '${filter}'`;
|
||||
qs.$filter = `displayName eq '${escapeFilterValue(filter)}'`;
|
||||
}
|
||||
response = await microsoftSharePointApiRequest.call(
|
||||
this,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { MockProxy } from 'jest-mock-extended';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IBinaryData, IExecuteSingleFunctions } from 'n8n-workflow';
|
||||
|
||||
import { downloadFilePostReceive } from '../../helpers/utils';
|
||||
import { downloadFilePostReceive, escapeFilterValue } from '../../helpers/utils';
|
||||
|
||||
describe('Microsoft SharePoint Node', () => {
|
||||
let executeSingleFunctions: MockProxy<IExecuteSingleFunctions>;
|
||||
@@ -51,4 +51,13 @@ describe('Microsoft SharePoint Node', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('escapeFilterValue', () => {
|
||||
it('should escape single quotes', () => {
|
||||
expect(escapeFilterValue("hello' there ''")).toEqual("hello'' there ''''");
|
||||
});
|
||||
it('should not escape double quotes', () => {
|
||||
expect(escapeFilterValue('hello " there ""')).toEqual('hello " there ""');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,7 +75,7 @@ describe('Microsoft SharePoint Node', () => {
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({
|
||||
qs: {
|
||||
$filter: "name eq 'file'",
|
||||
$filter: "startswith(name, 'file')",
|
||||
},
|
||||
});
|
||||
expect(listSearchResult).toEqual({
|
||||
@@ -88,6 +88,26 @@ describe('Microsoft SharePoint Node', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('properly escapes filter parameter', async () => {
|
||||
const mockResponse = {
|
||||
'@odata.nextLink':
|
||||
'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/items?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB',
|
||||
value: [],
|
||||
};
|
||||
mockRequestWithAuthentication.mockReturnValue(mockResponse);
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site');
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('folder');
|
||||
|
||||
await node.methods.listSearch.getFiles.call(loadOptionsFunctions, "fi'le'");
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({
|
||||
qs: {
|
||||
$filter: "startswith(name, 'fi''le''')",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should list search files with pagination', async () => {
|
||||
const mockResponse = {
|
||||
value: [
|
||||
@@ -166,7 +186,7 @@ describe('Microsoft SharePoint Node', () => {
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({
|
||||
qs: {
|
||||
$filter: "name eq 'folder'",
|
||||
$filter: 'folder ne null',
|
||||
},
|
||||
});
|
||||
expect(listSearchResult).toEqual({
|
||||
@@ -278,6 +298,26 @@ describe('Microsoft SharePoint Node', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('properly escapes filter parameter', async () => {
|
||||
const mockResponse = {
|
||||
'@odata.nextLink':
|
||||
'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/listItems?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB',
|
||||
value: [],
|
||||
};
|
||||
mockRequestWithAuthentication.mockReturnValue(mockResponse);
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site');
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('list');
|
||||
|
||||
await node.methods.listSearch.getItems.call(loadOptionsFunctions, "Ti'le'");
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({
|
||||
qs: {
|
||||
$filter: "fields/Title eq 'Ti''le'''",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should list search items with pagination', async () => {
|
||||
const mockResponse = {
|
||||
value: [
|
||||
@@ -362,6 +402,25 @@ describe('Microsoft SharePoint Node', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('properly escapes filter parameter', async () => {
|
||||
const mockResponse = {
|
||||
'@odata.nextLink':
|
||||
'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/lists?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB',
|
||||
value: [],
|
||||
};
|
||||
mockRequestWithAuthentication.mockReturnValue(mockResponse);
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site');
|
||||
|
||||
await node.methods.listSearch.getLists.call(loadOptionsFunctions, "' or '1'='1");
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({
|
||||
qs: {
|
||||
$filter: "displayName eq ''' or ''1''=''1'",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should list search lists with pagination', async () => {
|
||||
const mockResponse = {
|
||||
value: [
|
||||
|
||||
Reference in New Issue
Block a user