Skip to content

Commit

Permalink
Merge branch 'development' into 6675-translations-bug
Browse files Browse the repository at this point in the history
  • Loading branch information
konzz authored Jul 26, 2024
2 parents 1521224 + 44a6d10 commit c976543
Show file tree
Hide file tree
Showing 23 changed files with 1,310 additions and 1,131 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ app/api/csv/specs/*.jpg
!app/api/files/specs/uploads/f2082bf51b6ef839690485d7153e847a.pdf
!app/api/files/specs/uploads/f2082bf51b6ef839690485d7153e847b.pdf
!app/api/files/specs/customUploads/customPDF.pdf
!app/api/files/specs/uploads/fileOnPublicEntity.pdf
!app/api/files/specs/uploads/eng.pdf
!app/api/files/specs/uploads/spn.pdf
!app/api/files/specs/uploads/import.zip
Expand Down Expand Up @@ -55,4 +56,4 @@ cypress/snapshots/diff/*
cypress/videos/*
*.diff.png
*.actual.png

.trunk/*
85 changes: 58 additions & 27 deletions app/api/files/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,52 @@ import entities from 'api/entities';
import { processDocument } from 'api/files/processDocument';
import { uploadMiddleware } from 'api/files/uploadMiddleware';
import { legacyLogger } from 'api/log';
import { FileType } from 'shared/types/fileType';
import { fileSchema } from 'shared/types/fileSchema';
import { permissionsContext } from 'api/permissions/permissionsContext';
import { validateAndCoerceRequest } from 'api/utils/validateRequest';
import { files } from './files';
import { EntitySchema } from 'shared/types/entityType';
import { fileSchema } from 'shared/types/fileSchema';
import { FileType } from 'shared/types/fileType';
import { UserSchema } from 'shared/types/userType';
import { createError, handleError, validation } from '../utils';
import { files } from './files';
import { storage } from './storage';

const checkEntityPermission = async (file: FileType): Promise<boolean> => {
if (!file.entity) return true;
const relatedEntities = await entities.get({ sharedId: file.entity }, '_id', {
withoutDocuments: true,
});
return !!relatedEntities.length;
const checkEntityPermission = async (
file: FileType,
user: UserSchema | undefined,
level: 'read' | 'write' = 'read'
): Promise<boolean> => {
if (['admin'].includes(user?.role || '')) return true;
const [fileInDB] = await files.get({ _id: file._id });

if (!fileInDB || (fileInDB.type === 'custom' && level === 'write')) {
return false;
}

if (fileInDB.type === 'custom' && level === 'read') {
return true;
}

const relatedEntities: EntitySchema[] = await entities.get(
{ sharedId: fileInDB.entity },
'_id, permissions',
{ withoutDocuments: true }
);

if (level === 'read') {
return relatedEntities.length > 0;
}

return (
relatedEntities.length > 0 &&
relatedEntities.every(
entity =>
!!(entity.permissions || []).find(
permission =>
permission.refId.toString() === user?._id?.toString() && permission.level === 'write'
)
)
);
};

const filterByEntityPermissions = async (fileList: FileType[]): Promise<FileType[]> => {
Expand Down Expand Up @@ -57,7 +90,7 @@ export default (app: Application) => {

app.post(
'/api/files/upload/custom',
needsAuthorization(['admin', 'editor', 'collaborator']),
needsAuthorization(['admin']),
uploadMiddleware('custom'),
activitylogMiddleware,
(req, res, next) => {
Expand Down Expand Up @@ -98,13 +131,14 @@ export default (app: Application) => {
body: fileSchema,
},
}),
(req, res, next) => {
files
.save(req.body)
.then(result => {
res.json(result);
})
.catch(next);
async (req, res) => {
if (
!(await checkEntityPermission(req.body, permissionsContext.getUserInContext(), 'write'))
) {
throw createError('file not found', 404);
}
const result = await files.save(req.body);
res.json(result);
}
);

Expand Down Expand Up @@ -192,7 +226,7 @@ export default (app: Application) => {
!file?.filename ||
!file?.type ||
!(await storage.fileExists(file.filename, file.type)) ||
!(await checkEntityPermission(file))
!(await checkEntityPermission(file, permissionsContext.getUserInContext()))
) {
throw createError('file not found', 404);
}
Expand Down Expand Up @@ -235,7 +269,10 @@ export default (app: Application) => {

async (req: Request<{}, {}, {}, { _id: string }>, res) => {
const [fileToDelete] = await files.get({ _id: req.query._id });
if (!fileToDelete || !(await checkEntityPermission(fileToDelete))) {
if (
!fileToDelete ||
!(await checkEntityPermission(fileToDelete, permissionsContext.getUserInContext(), 'write'))
) {
throw createError('file not found', 404);
}

Expand All @@ -262,14 +299,8 @@ export default (app: Application) => {
},
},
}),
(req, res, next) => {
files
.get(req.query)
.then(async result => filterByEntityPermissions(result))
.then(result => {
res.json(result);
})
.catch(next);
async (req, res) => {
res.json(await filterByEntityPermissions(await files.get(req.query)));
}
);

Expand Down
31 changes: 24 additions & 7 deletions app/api/files/specs/downloadRoute.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
writerUser,
adminUser,
customPdfFileName,
fileOnPublicEntity,
} from './fixtures';

import uploadRoutes from '../routes';
Expand All @@ -38,10 +39,9 @@ describe('files routes download', () => {

describe('GET/', () => {
it.each([fileName1, customPdfFileName])('should send the file (%s)', async filename => {
const response: SuperTestResponse = await request(app)
.get(`/api/files/${filename}`)
.expect(200);
const response = await request(app).get(`/api/files/${filename}`);

expect(response.status).toBe(200);
expect(response.body instanceof Buffer).toBe(true);
});

Expand Down Expand Up @@ -101,18 +101,35 @@ describe('files routes download', () => {
});
});

describe('when there is no user logged in', () => {
it('should serve custom files', async () => {
testingEnvironment.resetPermissions();
const response = await request(app).get(`/api/files/${customPdfFileName}`);

expect(response.status).toBe(200);
});
it('should serve files that are related to public entities', async () => {
testingEnvironment.resetPermissions();
const response = await request(app).get(`/api/files/${fileOnPublicEntity}`);

expect(response.status).toBe(200);
});
});

describe('when the related entity is restricted by permissions', () => {
it('should return a 404 if the user does not have permission', async () => {
app = setAppWithUser(uploadRoutes, collabUser);
await request(app).get(`/api/files/${restrictedFileName}`).expect(404);
const response = await request(app).get(`/api/files/${restrictedFileName}`);
expect(response.status).toBe(404);
});

it('should return the file if the user has permission', async () => {
app = setAppWithUser(uploadRoutes, writerUser);
const response: SuperTestResponse = await request(app)
.get(`/api/files/${restrictedFileName}`)
.expect(200);
const response: SuperTestResponse = await request(app).get(
`/api/files/${restrictedFileName}`
);

expect(response.status).toBe(200);
expect(response.body instanceof Buffer).toBe(true);
});

Expand Down
59 changes: 59 additions & 0 deletions app/api/files/specs/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ const fixturesFactory = getFixturesFactory();
const entityId = db.id();
const entityEnId = db.id();
const restrictedEntityId = db.id();
const readOnlyEntity = db.id();
const uploadId = db.id();
const uploadId2 = db.id();
const restrictedUploadId = db.id();
const restrictedUploadId2 = db.id();
const readOnlyUploadId = db.id();
const customFileId = db.id();
const templateId = fixturesFactory.id('template');
const importTemplate = db.id();
const writerUserId = db.id();
const externalUrlFileId = db.id();
const fileName1 = 'f2082bf51b6ef839690485d7153e847a.pdf';
const fileOnPublicEntity = 'fileOnPublicEntity.pdf';
const restrictedFileName = 'f2082bf51b6ef839690485d7153e847b.pdf';
const customPdfFileName = 'customPDF.pdf';

Expand All @@ -40,6 +44,17 @@ const adminUser = {

const fixtures: DBFixture = {
files: [
{
_id: db.id(),
creationDate: 1,
entity: 'publicEntity',
generatedToc: true,
originalname: 'publicEntityFile',
filename: fileOnPublicEntity,
mimetype: 'application/pdf',
type: 'document',
language: 'eng',
},
{
_id: uploadId,
creationDate: 1,
Expand Down Expand Up @@ -68,6 +83,15 @@ const fixtures: DBFixture = {
type: 'custom',
language: 'eng',
},
{
_id: customFileId,
entity: 'restrictedSharedId',
originalname: 'customPdf',
filename: 'custom_file.pdf',
mimetype: 'application/pdf',
type: 'custom',
language: 'eng',
},
{
_id: restrictedUploadId,
entity: 'restrictedSharedId',
Expand All @@ -87,6 +111,15 @@ const fixtures: DBFixture = {
type: 'document',
language: 'eng',
},
{
_id: readOnlyUploadId,
entity: 'readOnlySharedId',
generatedToc: true,
originalname: 'readOnlyUpload',
filename: 'read only file',
type: 'document',
language: 'eng',
},
{
entity: 'sharedId1',
filename: 'fileWithoutTocFlag',
Expand All @@ -108,6 +141,14 @@ const fixtures: DBFixture = {
{ entity: 'sharedId1', file: uploadId.toString() },
],
entities: [
{
_id: db.id(),
sharedId: 'publicEntity',
language: 'es',
title: 'Public entity',
template: templateId,
published: true,
},
{
_id: entityId,
sharedId: 'sharedId1',
Expand Down Expand Up @@ -138,6 +179,21 @@ const fixtures: DBFixture = {
},
],
},
{
_id: readOnlyEntity,
template: templateId,
sharedId: 'readOnlySharedId',
language: 'en',
title: 'Read only shared id',
public: false,
permissions: [
{
refId: writerUserId.toString(),
type: 'user',
level: 'read',
},
],
},
],
templates: [
{ _id: templateId, default: true, name: 'mydoc', properties: [] },
Expand Down Expand Up @@ -217,12 +273,15 @@ export {
customPdfFileName,
uploadId,
uploadId2,
customFileId,
restrictedUploadId,
restrictedUploadId2,
readOnlyUploadId,
templateId,
importTemplate,
collabUser,
adminUser,
writerUser,
externalUrlFileId,
fileOnPublicEntity,
};
Loading

0 comments on commit c976543

Please sign in to comment.