Files
overleaf-cep/services/web/test/acceptance/src/AuthorizationTests.mjs
T
Andrew Rumble beb6f6d484 Merge pull request #29597 from overleaf/ar-last-features-esm-conversion
[web] last features esm conversion

GitOrigin-RevId: a35ab995bf654f1cdfe0e0062d8806761ecccf2d
2025-11-21 09:05:36 +00:00

641 lines
22 KiB
JavaScript

import { expect } from 'chai'
import UserHelper from './helpers/User.mjs'
import request from './helpers/request.js'
import settings from '@overleaf/settings'
import Features from '../../../app/src/infrastructure/Features.mjs'
import expectErrorResponse from './helpers/expectErrorResponse.mjs'
import { promisify } from '@overleaf/promise-utils'
const User = UserHelper.promises
async function tryReadAccess(user, projectId, test) {
const projectRequest = await user.doRequest('get', `/project/${projectId}`)
test(projectRequest.response, projectRequest.body)
const zipRequest = await user.doRequest(
'get',
`/project/${projectId}/download/zip`
)
test(zipRequest.response, zipRequest.body)
}
async function tryRenameProjectAccess(user, projectId, test) {
const { response, body } = await user.doRequest('post', {
url: `/project/${projectId}/settings`,
json: { name: 'new name' },
})
test(response, body)
}
async function trySettingsWriteAccess(user, projectId, test) {
const { response, body } = await user.doRequest('post', {
url: `/project/${projectId}/settings`,
json: { compiler: 'latex' },
})
test(response, body)
}
async function tryProjectAdminAccess(user, projectId, test) {
const renameRequest = await user.doRequest('post', {
url: `/project/${projectId}/rename`,
json: { newProjectName: 'new-name' },
})
test(renameRequest.response, renameRequest.body)
const settingsRequest = await user.doRequest('post', {
url: `/project/${projectId}/settings/admin`,
json: { publicAccessLevel: 'private' },
})
test(settingsRequest.response, settingsRequest.body)
}
async function tryAdminAccess(user, test) {
const { response, body } = await user.doRequest('get', '/admin')
test(response, body)
if (Features.hasFeature('saas')) {
const { response, body } = await user.doRequest(
'get',
`/admin/user/${user._id}`
)
test(response, body)
}
}
function tryContentAccessCb(user, projectId, test, callback) {
// The real-time service calls this end point to determine the user's
// permissions.
let userId
if (user.id != null) {
userId = user.id
} else {
userId = 'anonymous-user'
}
request.post(
{
url: `/project/${projectId}/join`,
auth: {
user: settings.apis.web.user,
pass: settings.apis.web.pass,
sendImmediately: true,
},
json: { userId },
jar: false,
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
test(response, body)
callback()
}
)
}
const tryContentAccess = promisify(tryContentAccessCb)
async function expectAdminAccess(user) {
await tryAdminAccess(user, response =>
expect(response.statusCode).to.be.oneOf([200, 204])
)
}
async function expectRedirectedAdminAccess(user) {
await tryAdminAccess(user, response => {
expect(response.statusCode).to.equal(302)
expect(response.headers.location).to.equal(
settings.adminUrl + response.request.uri.pathname
)
})
}
async function expectReadAccess(user, projectId) {
await tryReadAccess(user, projectId, response =>
expect(response.statusCode).to.be.oneOf([200, 204])
)
await tryContentAccess(user, projectId, (response, body) =>
expect(body.privilegeLevel).to.be.oneOf([
'owner',
'readAndWrite',
'readOnly',
])
)
}
async function expectContentWriteAccess(user, projectId) {
await tryContentAccess(user, projectId, (response, body) =>
expect(body.privilegeLevel).to.be.oneOf(['owner', 'readAndWrite'])
)
}
async function expectRenameProjectAccess(user, projectId) {
await tryRenameProjectAccess(user, projectId, response => {
expect(response.statusCode).to.be.oneOf([200, 204])
})
}
async function expectSettingsWriteAccess(user, projectId) {
await trySettingsWriteAccess(user, projectId, response =>
expect(response.statusCode).to.be.oneOf([200, 204])
)
}
async function expectProjectAdminAccess(user, projectId) {
await tryProjectAdminAccess(user, projectId, response =>
expect(response.statusCode).to.be.oneOf([200, 204])
)
}
async function expectNoReadAccess(user, projectId) {
await tryReadAccess(user, projectId, expectErrorResponse.restricted.html)
await tryContentAccess(user, projectId, (response, body) => {
expect(response.statusCode).to.equal(403)
expect(body).to.equal('Forbidden')
})
}
async function expectNoContentWriteAccess(user, projectId) {
await tryContentAccess(user, projectId, (response, body) =>
expect(body.privilegeLevel).to.be.oneOf([undefined, null, 'readOnly'])
)
}
async function expectNoSettingsWriteAccess(user, projectId) {
await trySettingsWriteAccess(
user,
projectId,
expectErrorResponse.restricted.json
)
}
async function expectNoRenameProjectAccess(user, projectId) {
await tryRenameProjectAccess(
user,
projectId,
expectErrorResponse.restricted.json
)
}
async function expectNoProjectAdminAccess(user, projectId) {
await tryProjectAdminAccess(user, projectId, response => {
expect(response.statusCode).to.equal(403)
})
}
async function expectNoAnonymousProjectAdminAccess(user, projectId) {
await tryProjectAdminAccess(
user,
projectId,
expectErrorResponse.requireLogin.json
)
}
async function expectChatAccess(user, projectId) {
const { response } = await user.doRequest(
'get',
`/project/${projectId}/messages`
)
expect(response.statusCode).to.equal(200)
}
async function expectNoChatAccess(user, projectId) {
const { response } = await user.doRequest(
'get',
`/project/${projectId}/messages`
)
expect(response.statusCode).to.equal(403)
}
describe('Authorization', function () {
beforeEach(async function () {
this.timeout(90000)
this.owner = new User()
this.other1 = new User()
this.other2 = new User()
this.anon = new User()
this.site_admin = new User({ email: 'admin@example.com' })
settings.adminRolesEnabled = false
await Promise.all([
this.owner.login(),
this.other1.login(),
this.other2.login(),
this.anon.getCsrfToken(),
(async () => {
await this.site_admin.ensureUserExists()
await this.site_admin.ensureAdmin()
await this.site_admin.login()
})(),
])
})
describe('private project', function () {
beforeEach(async function () {
this.projectId = await this.owner.createProject('private-project')
})
it('should allow the owner read access to it', async function () {
await expectReadAccess(this.owner, this.projectId)
})
it('should allow the owner write access to its content', async function () {
await expectContentWriteAccess(this.owner, this.projectId)
})
it('should allow the owner write access to its settings', async function () {
await expectSettingsWriteAccess(this.owner, this.projectId)
})
it('should allow the owner to rename the project', async function () {
await expectRenameProjectAccess(this.owner, this.projectId)
})
it('should allow the owner project admin access to it', async function () {
await expectProjectAdminAccess(this.owner, this.projectId)
})
it('should allow the owner user chat messages access', async function () {
await expectChatAccess(this.owner, this.projectId)
})
it('should not allow another user read access to the project', async function () {
await expectNoReadAccess(this.other1, this.projectId)
})
it('should not allow another user write access to its content', async function () {
await expectNoContentWriteAccess(this.other1, this.projectId)
})
it('should not allow another user write access to its settings', async function () {
await expectNoSettingsWriteAccess(this.other1, this.projectId)
})
it('should not allow another user to rename the project', async function () {
await expectNoRenameProjectAccess(this.other1, this.projectId)
})
it('should not allow another user project admin access to it', async function () {
await expectNoProjectAdminAccess(this.other1, this.projectId)
})
it('should not allow another user chat messages access', async function () {
await expectNoChatAccess(this.other1, this.projectId)
})
it('should not allow anonymous user read access to it', async function () {
await expectNoReadAccess(this.anon, this.projectId)
})
it('should not allow anonymous user write access to its content', async function () {
await expectNoContentWriteAccess(this.anon, this.projectId)
})
it('should not allow anonymous user write access to its settings', async function () {
await expectNoSettingsWriteAccess(this.anon, this.projectId)
})
it('should not allow anonymous user to rename the project', async function () {
await expectNoRenameProjectAccess(this.anon, this.projectId)
})
it('should not allow anonymous user project admin access to it', async function () {
await expectNoAnonymousProjectAdminAccess(this.anon, this.projectId)
})
it('should not allow anonymous user chat messages access', async function () {
await expectNoChatAccess(this.anon, this.projectId)
})
describe('with admin privilege available', function () {
beforeEach(function () {
settings.adminPrivilegeAvailable = true
})
it('should allow site admin users read access to it', async function () {
await expectReadAccess(this.site_admin, this.projectId)
})
it('should allow site admin users write access to its content', async function () {
await expectContentWriteAccess(this.site_admin, this.projectId)
})
it('should allow site admin users write access to its settings', async function () {
await expectSettingsWriteAccess(this.site_admin, this.projectId)
})
it('should allow site admin users to rename the project', async function () {
await expectRenameProjectAccess(this.site_admin, this.projectId)
})
it('should allow site admin users project admin access to it', async function () {
await expectProjectAdminAccess(this.site_admin, this.projectId)
})
it('should allow site admin users site admin access to site admin endpoints', async function () {
await expectAdminAccess(this.site_admin)
})
})
describe('with admin privilege unavailable', function () {
beforeEach(function () {
settings.adminPrivilegeAvailable = false
})
afterEach(function () {
settings.adminPrivilegeAvailable = true
})
it('should not allow site admin users read access to it', async function () {
await expectNoReadAccess(this.site_admin, this.projectId)
})
it('should not allow site admin users write access to its content', async function () {
await expectNoContentWriteAccess(this.site_admin, this.projectId)
})
it('should not allow site admin users write access to its settings', async function () {
await expectNoSettingsWriteAccess(this.site_admin, this.projectId)
})
it('should not allow site admin users to rename the project', async function () {
await expectNoRenameProjectAccess(this.site_admin, this.projectId)
})
it('should not allow site admin users project admin access to it', async function () {
await expectNoProjectAdminAccess(this.site_admin, this.projectId)
})
it('should redirect site admin users when accessing site admin endpoints', async function () {
await expectRedirectedAdminAccess(this.site_admin)
})
})
describe('with admin roles', function () {
beforeEach(function () {
if (!settings.moduleImportSequence.includes('admin-roles')) {
this.skip()
}
settings.adminRolesEnabled = true
settings.adminPrivilegeAvailable = true
})
afterEach(function () {
settings.adminRolesEnabled = false
settings.adminPrivilegeAvailable = true
this.site_admin.mongoUpdate({
$set: { adminRoles: [] },
})
})
describe('engineering', function () {
beforeEach(function () {
this.site_admin.mongoUpdate({
$set: { adminRoles: ['engineering'] },
})
})
it('should allow site admin users read access to it', async function () {
await expectReadAccess(this.site_admin, this.projectId)
})
it('should not allow site admin users write access to its content', async function () {
await expectNoContentWriteAccess(this.site_admin, this.projectId)
})
it('should allow site admin users write access to its settings', async function () {
await expectSettingsWriteAccess(this.site_admin, this.projectId)
})
it('should allow site admin users to rename the project', async function () {
await expectRenameProjectAccess(this.site_admin, this.projectId)
})
it('should allow site admin users project admin access to it', async function () {
await expectProjectAdminAccess(this.site_admin, this.projectId)
})
it('should allow site admin users site admin access to site admin endpoints', async function () {
await expectAdminAccess(this.site_admin)
})
})
describe('no admin role assigned', function () {
beforeEach(function () {
this.site_admin.mongoUpdate({
$set: { adminRoles: [] },
})
})
it('should not allow site admin users read access to it', async function () {
await expectNoReadAccess(this.site_admin, this.projectId)
})
it('should not allow site admin users write access to its content', async function () {
await expectNoContentWriteAccess(this.site_admin, this.projectId)
})
it('should not allow site admin users write access to its settings', async function () {
await expectNoSettingsWriteAccess(this.site_admin, this.projectId)
})
it('should not allow site admin users to rename the project', async function () {
await expectNoRenameProjectAccess(this.site_admin, this.projectId)
})
it('should not allow site admin users project admin access to it', async function () {
await expectNoProjectAdminAccess(this.site_admin, this.projectId)
})
it('should allow site admin users site admin access to site admin endpoints', async function () {
await expectAdminAccess(this.site_admin)
})
})
})
})
describe('shared project', function () {
beforeEach(async function () {
this.rw_user = this.other1
this.ro_user = this.other2
this.projectId = await this.owner.createProject('private-project')
await this.owner.addUserToProject(
this.projectId,
this.ro_user,
'readOnly'
)
await this.owner.addUserToProject(
this.projectId,
this.rw_user,
'readAndWrite'
)
})
it('should allow the read-only user read access to it', async function () {
await expectReadAccess(this.ro_user, this.projectId)
})
it('should allow the read-only user chat messages access', async function () {
await expectChatAccess(this.ro_user, this.projectId)
})
it('should not allow the read-only user write access to its content', async function () {
await expectNoContentWriteAccess(this.ro_user, this.projectId)
})
it('should not allow the read-only user write access to its settings', async function () {
await expectNoSettingsWriteAccess(this.ro_user, this.projectId)
})
it('should not allow the read-only user to rename the project', async function () {
await expectNoRenameProjectAccess(this.ro_user, this.projectId)
})
it('should not allow the read-only user project admin access to it', async function () {
await expectNoProjectAdminAccess(this.ro_user, this.projectId)
})
it('should allow the read-write user read access to it', async function () {
await expectReadAccess(this.rw_user, this.projectId)
})
it('should allow the read-write user write access to its content', async function () {
await expectContentWriteAccess(this.rw_user, this.projectId)
})
it('should allow the read-write user write access to its settings', async function () {
await expectSettingsWriteAccess(this.rw_user, this.projectId)
})
it('should not allow the read-write user to rename the project', async function () {
await expectNoRenameProjectAccess(this.rw_user, this.projectId)
})
it('should not allow the read-write user project admin access to it', async function () {
await expectNoProjectAdminAccess(this.rw_user, this.projectId)
})
it('should allow the read-write user chat messages access', async function () {
await expectChatAccess(this.rw_user, this.projectId)
})
})
describe('public read-write project', function () {
/**
* Note: this is a test for the legacy "public access" feature.
* See documentation comment in `Authorization/PublicAccessLevels`
* */
beforeEach(async function () {
this.projectId = await this.owner.createProject('public-rw-project')
await this.owner.makePublic(this.projectId, 'readAndWrite')
})
it('should allow a user read access to it', async function () {
await expectReadAccess(this.other1, this.projectId)
})
it('should allow a user write access to its content', async function () {
await expectContentWriteAccess(this.other1, this.projectId)
})
it('should allow a user chat messages access', async function () {
await expectChatAccess(this.other1, this.projectId)
})
it('should not allow a user write access to its settings', async function () {
await expectNoSettingsWriteAccess(this.other1, this.projectId)
})
it('should not allow a user to rename the project', async function () {
await expectNoRenameProjectAccess(this.other1, this.projectId)
})
it('should not allow a user project admin access to it', async function () {
await expectNoProjectAdminAccess(this.other1, this.projectId)
})
it('should allow an anonymous user read access to it', async function () {
await expectReadAccess(this.anon, this.projectId)
})
it('should allow an anonymous user write access to its content', async function () {
await expectContentWriteAccess(this.anon, this.projectId)
})
it('should allow an anonymous user chat messages access', async function () {
// chat access for anonymous users is a CE/SP-only feature, although currently broken
// https://github.com/overleaf/internal/issues/10944
if (Features.hasFeature('saas')) {
this.skip()
}
await expectChatAccess(this.anon, this.projectId)
})
it('should not allow an anonymous user write access to its settings', async function () {
await expectNoSettingsWriteAccess(this.anon, this.projectId)
})
it('should not allow an anonymous user to rename the project', async function () {
await expectNoRenameProjectAccess(this.anon, this.projectId)
})
it('should not allow an anonymous user project admin access to it', async function () {
await expectNoAnonymousProjectAdminAccess(this.anon, this.projectId)
})
})
describe('public read-only project', function () {
/**
* Note: this is a test for the legacy "public access" feature.
* See documentation comment in `Authorization/PublicAccessLevels`
* */
beforeEach(async function () {
this.projectId = await this.owner.createProject('public-ro-project')
await this.owner.makePublic(this.projectId, 'readOnly')
})
it('should allow a user read access to it', async function () {
await expectReadAccess(this.other1, this.projectId)
})
it('should not allow a user write access to its content', async function () {
await expectNoContentWriteAccess(this.other1, this.projectId)
})
it('should not allow a user write access to its settings', async function () {
await expectNoSettingsWriteAccess(this.other1, this.projectId)
})
it('should not allow a user to rename the project', async function () {
await expectNoRenameProjectAccess(this.other1, this.projectId)
})
it('should not allow a user project admin access to it', async function () {
await expectNoProjectAdminAccess(this.other1, this.projectId)
})
// NOTE: legacy readOnly access does not count as 'restricted' in the new model
it('should allow a user chat messages access', async function () {
await expectChatAccess(this.other1, this.projectId)
})
it('should allow an anonymous user read access to it', async function () {
await expectReadAccess(this.anon, this.projectId)
})
it('should not allow an anonymous user write access to its content', async function () {
await expectNoContentWriteAccess(this.anon, this.projectId)
})
it('should not allow an anonymous user write access to its settings', async function () {
await expectNoSettingsWriteAccess(this.anon, this.projectId)
})
it('should not allow an anonymous user to rename the project', async function () {
await expectNoRenameProjectAccess(this.anon, this.projectId)
})
it('should not allow an anonymous user project admin access to it', async function () {
await expectNoAnonymousProjectAdminAccess(this.anon, this.projectId)
})
it('should not allow an anonymous user chat messages access', async function () {
await expectNoChatAccess(this.anon, this.projectId)
})
})
})