mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix: Check license in folder controller (no-changelog) (#16937)
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
Query,
|
||||
Put,
|
||||
Param,
|
||||
Licensed,
|
||||
} from '@n8n/decorators';
|
||||
import { Response } from 'express';
|
||||
import { UserError } from 'n8n-workflow';
|
||||
@@ -37,6 +38,7 @@ export class ProjectController {
|
||||
|
||||
@Post('/')
|
||||
@ProjectScope('folder:create')
|
||||
@Licensed('feat:folders')
|
||||
async createFolder(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@@ -55,6 +57,7 @@ export class ProjectController {
|
||||
|
||||
@Get('/:folderId/tree')
|
||||
@ProjectScope('folder:read')
|
||||
@Licensed('feat:folders')
|
||||
async getFolderTree(
|
||||
req: AuthenticatedRequest<{ projectId: string; folderId: string }>,
|
||||
_res: Response,
|
||||
@@ -74,6 +77,7 @@ export class ProjectController {
|
||||
|
||||
@Get('/:folderId/credentials')
|
||||
@ProjectScope('folder:read')
|
||||
@Licensed('feat:folders')
|
||||
async getFolderUsedCredentials(
|
||||
req: AuthenticatedRequest<{ projectId: string; folderId: string }>,
|
||||
_res: Response,
|
||||
@@ -97,6 +101,7 @@ export class ProjectController {
|
||||
|
||||
@Patch('/:folderId')
|
||||
@ProjectScope('folder:update')
|
||||
@Licensed('feat:folders')
|
||||
async updateFolder(
|
||||
req: AuthenticatedRequest<{ projectId: string; folderId: string }>,
|
||||
_res: Response,
|
||||
@@ -118,6 +123,7 @@ export class ProjectController {
|
||||
|
||||
@Delete('/:folderId')
|
||||
@ProjectScope('folder:delete')
|
||||
@Licensed('feat:folders')
|
||||
async deleteFolder(
|
||||
req: AuthenticatedRequest<{ projectId: string; folderId: string }>,
|
||||
_res: Response,
|
||||
@@ -139,6 +145,7 @@ export class ProjectController {
|
||||
|
||||
@Get('/')
|
||||
@ProjectScope('folder:list')
|
||||
@Licensed('feat:folders')
|
||||
async listFolders(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
res: Response,
|
||||
@@ -153,6 +160,7 @@ export class ProjectController {
|
||||
|
||||
@Get('/:folderId/content')
|
||||
@ProjectScope('folder:read')
|
||||
@Licensed('feat:folders')
|
||||
async getFolderContent(req: AuthenticatedRequest<{ projectId: string; folderId: string }>) {
|
||||
const { projectId, folderId } = req.params;
|
||||
|
||||
@@ -174,6 +182,7 @@ export class ProjectController {
|
||||
|
||||
@Put('/:folderId/transfer')
|
||||
@ProjectScope('folder:move')
|
||||
@Licensed('feat:folders')
|
||||
async transferFolderToProject(
|
||||
req: AuthenticatedRequest,
|
||||
_res: unknown,
|
||||
|
||||
@@ -49,6 +49,8 @@ let workflowRepository: WorkflowRepository;
|
||||
const activeWorkflowManager = mockInstance(ActiveWorkflowManager);
|
||||
|
||||
beforeEach(async () => {
|
||||
testServer.license.enable('feat:folders');
|
||||
|
||||
await testDb.truncate(['Folder', 'SharedWorkflow', 'TagEntity', 'Project', 'ProjectRelation']);
|
||||
|
||||
projectRepository = Container.get(ProjectRepository);
|
||||
@@ -66,6 +68,18 @@ beforeEach(async () => {
|
||||
});
|
||||
|
||||
describe('POST /projects/:projectId/folders', () => {
|
||||
test('should now create folder if license does not allow it', async () => {
|
||||
testServer.license.disable('feat:folders');
|
||||
const project = await createTeamProject(undefined, owner);
|
||||
await linkUserToProject(member, project, 'project:viewer');
|
||||
|
||||
const payload = {
|
||||
name: 'Test Folder',
|
||||
};
|
||||
|
||||
await authMemberAgent.post(`/projects/${project.id}/folders`).send(payload).expect(403);
|
||||
});
|
||||
|
||||
test('should not create folder when project does not exist', async () => {
|
||||
const payload = {
|
||||
name: 'Test Folder',
|
||||
@@ -235,6 +249,32 @@ describe('POST /projects/:projectId/folders', () => {
|
||||
});
|
||||
|
||||
describe('GET /projects/:projectId/folders/:folderId/tree', () => {
|
||||
test('should not retrieve folder tree if license does not allow it', async () => {
|
||||
testServer.license.disable('feat:folders');
|
||||
|
||||
const project = await createTeamProject('test', owner);
|
||||
const rootFolder = await createFolder(project, { name: 'Root' });
|
||||
|
||||
const childFolder1 = await createFolder(project, {
|
||||
name: 'Child 1',
|
||||
parentFolder: rootFolder,
|
||||
});
|
||||
|
||||
await createFolder(project, {
|
||||
name: 'Child 2',
|
||||
parentFolder: rootFolder,
|
||||
});
|
||||
|
||||
const grandchildFolder = await createFolder(project, {
|
||||
name: 'Grandchild',
|
||||
parentFolder: childFolder1,
|
||||
});
|
||||
|
||||
await authOwnerAgent
|
||||
.get(`/projects/${project.id}/folders/${grandchildFolder.id}/tree`)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
test('should not get folder tree when project does not exist', async () => {
|
||||
await authOwnerAgent.get('/projects/non-existing-id/folders/some-folder-id/tree').expect(403);
|
||||
});
|
||||
@@ -311,6 +351,68 @@ describe('GET /projects/:projectId/folders/:folderId/tree', () => {
|
||||
});
|
||||
|
||||
describe('GET /projects/:projectId/folders/:folderId/credentials', () => {
|
||||
test('should not retrieve folder tree if license does not allow it', async () => {
|
||||
testServer.license.disable('feat:folders');
|
||||
|
||||
const project = await createTeamProject('test', owner);
|
||||
const rootFolder = await createFolder(project, { name: 'Root' });
|
||||
|
||||
const childFolder1 = await createFolder(project, {
|
||||
name: 'Child 1',
|
||||
parentFolder: rootFolder,
|
||||
});
|
||||
|
||||
await createFolder(project, {
|
||||
name: 'Child 2',
|
||||
parentFolder: rootFolder,
|
||||
});
|
||||
|
||||
const grandchildFolder = await createFolder(project, {
|
||||
name: 'Grandchild',
|
||||
parentFolder: childFolder1,
|
||||
});
|
||||
|
||||
for (const folder of [rootFolder, childFolder1, grandchildFolder]) {
|
||||
const credential = await createCredentials(
|
||||
{
|
||||
name: `Test credential ${folder.name}`,
|
||||
data: '',
|
||||
type: 'test',
|
||||
},
|
||||
project,
|
||||
);
|
||||
|
||||
await createWorkflow(
|
||||
{
|
||||
name: 'Test Workflow',
|
||||
parentFolder: folder,
|
||||
active: false,
|
||||
nodes: [
|
||||
{
|
||||
parameters: {},
|
||||
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
|
||||
typeVersion: 1.2,
|
||||
position: [0, 0],
|
||||
id: faker.string.uuid(),
|
||||
name: 'OpenAI Chat Model',
|
||||
credentials: {
|
||||
openAiApi: {
|
||||
id: credential.id,
|
||||
name: credential.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
owner,
|
||||
);
|
||||
}
|
||||
|
||||
await authOwnerAgent
|
||||
.get(`/projects/${project.id}/folders/${childFolder1.id}/credentials`)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
test('should not get folder credentials when project does not exist', async () => {
|
||||
await authOwnerAgent
|
||||
.get('/projects/non-existing-id/folders/some-folder-id/credentials')
|
||||
@@ -416,6 +518,23 @@ describe('GET /projects/:projectId/folders/:folderId/credentials', () => {
|
||||
});
|
||||
|
||||
describe('PATCH /projects/:projectId/folders/:folderId', () => {
|
||||
test('should not update folder if license does not allow it', async () => {
|
||||
testServer.license.disable('feat:folders');
|
||||
|
||||
const project = await createTeamProject(undefined, owner);
|
||||
const folder = await createFolder(project, { name: 'Original Name' });
|
||||
await linkUserToProject(member, project, 'project:editor');
|
||||
|
||||
const payload = {
|
||||
name: 'Updated Folder Name',
|
||||
};
|
||||
|
||||
await authMemberAgent
|
||||
.patch(`/projects/${project.id}/folders/${folder.id}`)
|
||||
.send(payload)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
test('should not update folder when project does not exist', async () => {
|
||||
const payload = {
|
||||
name: 'Updated Folder Name',
|
||||
@@ -868,6 +987,18 @@ describe('PATCH /projects/:projectId/folders/:folderId', () => {
|
||||
});
|
||||
|
||||
describe('DELETE /projects/:projectId/folders/:folderId', () => {
|
||||
test('should not delete folder if license does not allow it', async () => {
|
||||
testServer.license.disable('feat:folders');
|
||||
|
||||
const project = await createTeamProject(undefined, owner);
|
||||
const folder = await createFolder(project);
|
||||
|
||||
await authOwnerAgent
|
||||
.delete(`/projects/${project.id}/folders/${folder.id}`)
|
||||
.send({})
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
test('should not delete folder when project does not exist', async () => {
|
||||
await authOwnerAgent
|
||||
.delete('/projects/non-existing-id/folders/some-folder-id')
|
||||
@@ -1159,6 +1290,16 @@ describe('DELETE /projects/:projectId/folders/:folderId', () => {
|
||||
});
|
||||
|
||||
describe('GET /projects/:projectId/folders', () => {
|
||||
test('should not retrieve folder if license does not allow it', async () => {
|
||||
testServer.license.disable('feat:folders');
|
||||
|
||||
const project = await createTeamProject('test project', owner);
|
||||
await linkUserToProject(member, project, 'project:viewer');
|
||||
await createFolder(project, { name: 'Test Folder' });
|
||||
|
||||
await authMemberAgent.get(`/projects/${project.id}/folders`).expect(403);
|
||||
});
|
||||
|
||||
test('should not list folders when project does not exist', async () => {
|
||||
await authOwnerAgent.get('/projects/non-existing-id/folders').expect(403);
|
||||
});
|
||||
@@ -1570,6 +1711,16 @@ describe('GET /projects/:projectId/folders', () => {
|
||||
});
|
||||
|
||||
describe('GET /projects/:projectId/folders/content', () => {
|
||||
test('should not retrieve folder content if license does not allow it', async () => {
|
||||
testServer.license.disable('feat:folders');
|
||||
|
||||
const project = await createTeamProject('test project', owner);
|
||||
await linkUserToProject(member, project, 'project:viewer');
|
||||
const folder = await createFolder(project, { name: 'Test Folder' });
|
||||
|
||||
await authMemberAgent.get(`/projects/${project.id}/folders/${folder.id}/content`).expect(403);
|
||||
});
|
||||
|
||||
test('should not list folders when project does not exist', async () => {
|
||||
await authOwnerAgent
|
||||
.get('/projects/non-existing-id/folders/no-existing-id/content')
|
||||
@@ -1634,6 +1785,31 @@ describe('GET /projects/:projectId/folders/content', () => {
|
||||
});
|
||||
|
||||
describe('PUT /projects/:projectId/folders/:folderId/transfer', () => {
|
||||
test('should not transfer folder if license does not allow it', async () => {
|
||||
testServer.license.disable('feat:folders');
|
||||
|
||||
const admin = await createUser({ role: 'global:admin' });
|
||||
const sourceProject = await createTeamProject('source project', admin);
|
||||
const destinationProject = await createTeamProject('destination project', member);
|
||||
const sourceFolder1 = await createFolder(sourceProject, { name: 'Source Folder 1' });
|
||||
|
||||
const credential = await saveCredential(randomCredentialPayload(), {
|
||||
project: sourceProject,
|
||||
role: 'credential:owner',
|
||||
});
|
||||
|
||||
// ACT
|
||||
await testServer
|
||||
.authAgentFor(owner)
|
||||
.put(`/projects/${sourceProject.id}/folders/${sourceFolder1.id}/transfer`)
|
||||
.send({
|
||||
destinationProjectId: destinationProject.id,
|
||||
destinationParentFolderId: '0',
|
||||
shareCredentials: [credential.id],
|
||||
})
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
test('cannot transfer into the same project', async () => {
|
||||
const sourceProject = await createTeamProject('source project', member);
|
||||
const destinationProject = await createTeamProject('Team Project', member);
|
||||
|
||||
Reference in New Issue
Block a user