mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix: Add folder scopes to global owner and admin roles (#19230)
This commit is contained in:
@@ -78,6 +78,11 @@ export const GLOBAL_OWNER_SCOPES: Scope[] = [
|
|||||||
'project:delete',
|
'project:delete',
|
||||||
'insights:list',
|
'insights:list',
|
||||||
'folder:move',
|
'folder:move',
|
||||||
|
'folder:read',
|
||||||
|
'folder:update',
|
||||||
|
'folder:delete',
|
||||||
|
'folder:create',
|
||||||
|
'folder:list',
|
||||||
'oidc:manage',
|
'oidc:manage',
|
||||||
'dataStore:list',
|
'dataStore:list',
|
||||||
'role:manage',
|
'role:manage',
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ import {
|
|||||||
Put,
|
Put,
|
||||||
Param,
|
Param,
|
||||||
Licensed,
|
Licensed,
|
||||||
|
Middleware,
|
||||||
} from '@n8n/decorators';
|
} from '@n8n/decorators';
|
||||||
import { Response } from 'express';
|
import { NextFunction, Response } from 'express';
|
||||||
import { UserError } from 'n8n-workflow';
|
import { UserError } from 'n8n-workflow';
|
||||||
|
|
||||||
import { FolderNotFoundError } from '@/errors/folder-not-found.error';
|
import { FolderNotFoundError } from '@/errors/folder-not-found.error';
|
||||||
@@ -28,14 +29,32 @@ import { InternalServerError } from '@/errors/response-errors/internal-server.er
|
|||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
import { FolderService } from '@/services/folder.service';
|
import { FolderService } from '@/services/folder.service';
|
||||||
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
||||||
|
import { ProjectService } from '@/services/project.service.ee';
|
||||||
|
|
||||||
@RestController('/projects/:projectId/folders')
|
@RestController('/projects/:projectId/folders')
|
||||||
export class ProjectController {
|
export class ProjectController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly folderService: FolderService,
|
private readonly folderService: FolderService,
|
||||||
private readonly enterpriseWorkflowService: EnterpriseWorkflowService,
|
private readonly enterpriseWorkflowService: EnterpriseWorkflowService,
|
||||||
|
private readonly projectService: ProjectService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@Middleware()
|
||||||
|
async validateProjectExists(
|
||||||
|
req: AuthenticatedRequest<{ projectId: string }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { projectId } = req.params;
|
||||||
|
await this.projectService.getProject(projectId);
|
||||||
|
next();
|
||||||
|
} catch (e) {
|
||||||
|
res.status(404).send('Project not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/')
|
@Post('/')
|
||||||
@ProjectScope('folder:create')
|
@ProjectScope('folder:create')
|
||||||
@Licensed('feat:folders')
|
@Licensed('feat:folders')
|
||||||
@@ -44,8 +63,10 @@ export class ProjectController {
|
|||||||
_res: Response,
|
_res: Response,
|
||||||
@Body payload: CreateFolderDto,
|
@Body payload: CreateFolderDto,
|
||||||
) {
|
) {
|
||||||
|
const { projectId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const folder = await this.folderService.createFolder(payload, req.params.projectId);
|
const folder = await this.folderService.createFolder(payload, projectId);
|
||||||
return folder;
|
return folder;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof FolderNotFoundError) {
|
if (e instanceof FolderNotFoundError) {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ describe('POST /projects/:projectId/folders', () => {
|
|||||||
name: 'Test Folder',
|
name: 'Test Folder',
|
||||||
};
|
};
|
||||||
|
|
||||||
await authOwnerAgent.post('/projects/non-existing-id/folders').send(payload).expect(403);
|
await authOwnerAgent.post('/projects/non-existing-id/folders').send(payload).expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not create folder when name is empty', async () => {
|
test('should not create folder when name is empty', async () => {
|
||||||
@@ -278,7 +278,7 @@ describe('GET /projects/:projectId/folders/:folderId/tree', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should not get folder tree when project does not exist', async () => {
|
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);
|
await authOwnerAgent.get('/projects/non-existing-id/folders/some-folder-id/tree').expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not get folder tree when folder does not exist', async () => {
|
test('should not get folder tree when folder does not exist', async () => {
|
||||||
@@ -418,7 +418,7 @@ describe('GET /projects/:projectId/folders/:folderId/credentials', () => {
|
|||||||
test('should not get folder credentials when project does not exist', async () => {
|
test('should not get folder credentials when project does not exist', async () => {
|
||||||
await authOwnerAgent
|
await authOwnerAgent
|
||||||
.get('/projects/non-existing-id/folders/some-folder-id/credentials')
|
.get('/projects/non-existing-id/folders/some-folder-id/credentials')
|
||||||
.expect(403);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not get folder credentials when folder does not exist', async () => {
|
test('should not get folder credentials when folder does not exist', async () => {
|
||||||
@@ -545,7 +545,7 @@ describe('PATCH /projects/:projectId/folders/:folderId', () => {
|
|||||||
await authOwnerAgent
|
await authOwnerAgent
|
||||||
.patch('/projects/non-existing-id/folders/some-folder-id')
|
.patch('/projects/non-existing-id/folders/some-folder-id')
|
||||||
.send(payload)
|
.send(payload)
|
||||||
.expect(403);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not update folder when folder does not exist', async () => {
|
test('should not update folder when folder does not exist', async () => {
|
||||||
@@ -1005,7 +1005,7 @@ describe('DELETE /projects/:projectId/folders/:folderId', () => {
|
|||||||
await authOwnerAgent
|
await authOwnerAgent
|
||||||
.delete('/projects/non-existing-id/folders/some-folder-id')
|
.delete('/projects/non-existing-id/folders/some-folder-id')
|
||||||
.send({})
|
.send({})
|
||||||
.expect(403);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not delete folder when folder does not exist', async () => {
|
test('should not delete folder when folder does not exist', async () => {
|
||||||
@@ -1303,7 +1303,7 @@ describe('GET /projects/:projectId/folders', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should not list folders when project does not exist', async () => {
|
test('should not list folders when project does not exist', async () => {
|
||||||
await authOwnerAgent.get('/projects/non-existing-id/folders').expect(403);
|
await authOwnerAgent.get('/projects/non-existing-id/folders').expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not list folders if user has no access to project', async () => {
|
test('should not list folders if user has no access to project', async () => {
|
||||||
@@ -1731,7 +1731,7 @@ describe('GET /projects/:projectId/folders/content', () => {
|
|||||||
test('should not list folders when project does not exist', async () => {
|
test('should not list folders when project does not exist', async () => {
|
||||||
await authOwnerAgent
|
await authOwnerAgent
|
||||||
.get('/projects/non-existing-id/folders/no-existing-id/content')
|
.get('/projects/non-existing-id/folders/no-existing-id/content')
|
||||||
.expect(403);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not return folder content if user has no access to project', async () => {
|
test('should not return folder content if user has no access to project', async () => {
|
||||||
|
|||||||
@@ -853,6 +853,30 @@ describe('GET /project/:projectId', () => {
|
|||||||
role: 'project:admin',
|
role: 'project:admin',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should have correct folder scopes when, as an admin / owner, I fetch a project created by a different user', async () => {
|
||||||
|
const [ownerUser, testUser1] = await Promise.all([createOwner(), createUser()]);
|
||||||
|
|
||||||
|
const createdProject = await createTeamProject(undefined, testUser1);
|
||||||
|
|
||||||
|
const memberAgent = testServer.authAgentFor(ownerUser);
|
||||||
|
|
||||||
|
const resp = await memberAgent.get(`/projects/${createdProject.id}`);
|
||||||
|
expect(resp.status).toBe(200);
|
||||||
|
|
||||||
|
expect(resp.body.data.id).toBe(createdProject.id);
|
||||||
|
expect(resp.body.data.name).toBe(createdProject.name);
|
||||||
|
|
||||||
|
expect(resp.body.data.scopes).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
'folder:read',
|
||||||
|
'folder:update',
|
||||||
|
'folder:delete',
|
||||||
|
'folder:create',
|
||||||
|
'folder:list',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('DELETE /project/:projectId', () => {
|
describe('DELETE /project/:projectId', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user