mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-06 07:39:02 +02:00
Merge pull request #23020 from overleaf/ar-prevent-rootFolder-deletion
[web] Prevent deletes on a project's rootFolder GitOrigin-RevId: 6d0506f207425f65d3de990a78bb1ea9b136ed1e
This commit is contained in:
@@ -77,6 +77,12 @@ async function handleError(error, req, res, next) {
|
||||
res.status(400)
|
||||
plainTextResponse(res, error.message)
|
||||
}
|
||||
} else if (error instanceof Errors.NonDeletableEntityError) {
|
||||
req.logger.setLevel('warn')
|
||||
if (shouldSendErrorResponse) {
|
||||
res.status(422)
|
||||
plainTextResponse(res, error.message)
|
||||
}
|
||||
} else if (error instanceof Errors.SAMLSessionDataMissing) {
|
||||
req.logger.setLevel('warn')
|
||||
if (shouldSendErrorResponse) {
|
||||
|
||||
@@ -268,6 +268,12 @@ class InvalidInstitutionalEmailError extends OError {
|
||||
}
|
||||
}
|
||||
|
||||
class NonDeletableEntityError extends OError {
|
||||
get i18nKey() {
|
||||
return 'non_deletable_entity'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
OError,
|
||||
BackwardCompatibleError,
|
||||
@@ -318,4 +324,5 @@ module.exports = {
|
||||
AffiliationError,
|
||||
InvalidEmailError,
|
||||
InvalidInstitutionalEmailError,
|
||||
NonDeletableEntityError,
|
||||
}
|
||||
|
||||
@@ -375,11 +375,20 @@ async function moveEntity(projectId, entityId, destFolderId, entityType) {
|
||||
return { project, startPath, endPath, rev: entity.rev, changes }
|
||||
}
|
||||
|
||||
async function deleteEntity(projectId, entityId, entityType, callback) {
|
||||
async function deleteEntity(projectId, entityId, entityType) {
|
||||
const project = await ProjectGetter.promises.getProjectWithoutLock(
|
||||
projectId,
|
||||
{ name: true, rootFolder: true, overleaf: true, rootDoc_id: true }
|
||||
)
|
||||
if (
|
||||
entityType === 'folder' &&
|
||||
project.rootFolder.some(
|
||||
rootFolder => rootFolder._id.toString() === entityId.toString()
|
||||
)
|
||||
) {
|
||||
throw new Errors.NonDeletableEntityError('cannot delete root folder')
|
||||
}
|
||||
|
||||
const deleteRootDoc =
|
||||
project.rootDoc_id &&
|
||||
entityId &&
|
||||
|
||||
@@ -1352,6 +1352,7 @@
|
||||
"no_symbols_found": "No symbols found",
|
||||
"no_thanks_cancel_now": "No thanks, I still want to cancel",
|
||||
"no_update_email": "No, update email",
|
||||
"non_deletable_entity": "The specified entity may not be deleted",
|
||||
"normal": "Normal",
|
||||
"normally_x_price_per_month": "Normally __price__ per month",
|
||||
"normally_x_price_per_year": "Normally __price__ per year",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect } from 'chai'
|
||||
import chai, { expect } from 'chai'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import Path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
@@ -7,14 +7,14 @@ import ProjectGetter from '../../../app/src/Features/Project/ProjectGetter.js'
|
||||
import UserHelper from './helpers/User.mjs'
|
||||
import MockDocStoreApiClass from './mocks/MockDocstoreApi.mjs'
|
||||
import MockDocUpdaterApiClass from './mocks/MockDocUpdaterApi.mjs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
|
||||
const User = UserHelper.promises
|
||||
|
||||
const ObjectId = mongodb.ObjectId
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
let MockDocStoreApi, MockDocUpdaterApi
|
||||
|
||||
before(function () {
|
||||
@@ -76,7 +76,7 @@ describe('ProjectStructureChanges', function () {
|
||||
|
||||
async function uploadExampleProject(owner, zipFilename, options = {}) {
|
||||
const zipFile = fs.createReadStream(
|
||||
Path.resolve(Path.join(__dirname, '..', 'files', zipFilename))
|
||||
Path.resolve(Path.join(import.meta.dirname, '..', 'files', zipFilename))
|
||||
)
|
||||
|
||||
const { response, body } = await owner.doRequest('POST', {
|
||||
@@ -209,6 +209,47 @@ describe('ProjectStructureChanges', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleting folders', function () {
|
||||
beforeEach(async function () {
|
||||
const { projectId } = await createExampleProject(owner)
|
||||
this.exampleProjectId = projectId
|
||||
})
|
||||
describe('when the folder is the rootFolder', function () {
|
||||
beforeEach(async function () {
|
||||
const project = await ProjectGetter.promises.getProject(
|
||||
this.exampleProjectId
|
||||
)
|
||||
this.rootFolderId = project.rootFolder[0]._id
|
||||
})
|
||||
|
||||
it('should fail with a 422 error', async function () {
|
||||
await expect(
|
||||
deleteItem(owner, this.exampleProjectId, 'folder', this.rootFolderId)
|
||||
)
|
||||
.to.be.rejected.and.eventually.match(/status=422/)
|
||||
.and.eventually.match(/body="cannot delete root folder"/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the folder is not the rootFolder', function () {
|
||||
beforeEach(async function () {
|
||||
const folderId = await createExampleFolder(owner, this.exampleProjectId)
|
||||
this.exampleFolderId = folderId
|
||||
})
|
||||
|
||||
it('should succeed', async function () {
|
||||
await expect(
|
||||
deleteItem(
|
||||
owner,
|
||||
this.exampleProjectId,
|
||||
'folder',
|
||||
this.exampleFolderId
|
||||
)
|
||||
).to.be.fulfilled
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleting docs', function () {
|
||||
beforeEach(async function () {
|
||||
const { projectId } = await createExampleProject(owner)
|
||||
|
||||
Reference in New Issue
Block a user