overleaf/services/web/test/acceptance/src/AuthorizationTests.js

756 lines
21 KiB
JavaScript
Raw Normal View History

const { expect } = require('chai')
const async = require('async')
const User = require('./helpers/User')
const request = require('./helpers/request')
const settings = require('@overleaf/settings')
const Features = require('../../../app/src/infrastructure/Features')
const expectErrorResponse = require('./helpers/expectErrorResponse')
function tryReadAccess(user, projectId, test, callback) {
async.series(
[
cb =>
user.request.get(`/project/${projectId}`, (error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
cb()
}),
cb =>
user.request.get(
`/project/${projectId}/download/zip`,
(error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
cb()
}
),
],
callback
)
}
function tryRenameProjectAccess(user, projectId, test, callback) {
user.request.post(
{
uri: `/project/${projectId}/settings`,
json: {
name: 'new name',
},
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
test(response, body)
callback()
}
)
}
function trySettingsWriteAccess(user, projectId, test, callback) {
async.series(
[
cb =>
user.request.post(
{
uri: `/project/${projectId}/settings`,
json: {
compiler: 'latex',
},
},
(error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
cb()
}
),
],
callback
)
}
function tryProjectAdminAccess(user, projectId, test, callback) {
async.series(
[
cb =>
user.request.post(
{
uri: `/project/${projectId}/rename`,
json: {
newProjectName: 'new-name',
},
},
(error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
cb()
}
),
cb =>
user.request.post(
{
uri: `/project/${projectId}/settings/admin`,
json: {
publicAccessLevel: 'private',
},
},
(error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
cb()
}
),
],
callback
)
}
function tryAdminAccess(user, test, callback) {
async.series(
[
cb =>
user.request.get(
{
uri: '/admin',
},
(error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
cb()
}
),
cb => {
if (!Features.hasFeature('saas')) {
return cb()
}
user.request.get(
{
uri: `/admin/user/${user._id}`,
},
(error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
cb()
}
)
},
],
callback
)
}
function tryContentAccess(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`,
qs: { user_id: userId },
auth: {
user: settings.apis.web.user,
pass: settings.apis.web.pass,
sendImmediately: true,
},
json: true,
jar: false,
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
test(response, body)
callback()
}
)
}
function expectAdminAccess(user, callback) {
tryAdminAccess(
user,
response => expect(response.statusCode).to.be.oneOf([200, 204]),
callback
)
}
function expectRedirectedAdminAccess(user, callback) {
tryAdminAccess(
user,
response => {
expect(response.statusCode).to.equal(302)
expect(response.headers.location).to.equal(
settings.adminUrl + response.request.uri.pathname
)
},
callback
)
}
function expectReadAccess(user, projectId, callback) {
async.series(
[
cb =>
tryReadAccess(
user,
projectId,
(response, body) =>
expect(response.statusCode).to.be.oneOf([200, 204]),
cb
),
cb =>
tryContentAccess(
user,
projectId,
(response, body) =>
expect(body.privilegeLevel).to.be.oneOf([
'owner',
'readAndWrite',
'readOnly',
]),
cb
),
],
callback
)
}
function expectContentWriteAccess(user, projectId, callback) {
tryContentAccess(
user,
projectId,
(response, body) =>
expect(body.privilegeLevel).to.be.oneOf(['owner', 'readAndWrite']),
callback
)
}
function expectRenameProjectAccess(user, projectId, callback) {
tryRenameProjectAccess(
user,
projectId,
(response, body) => {
expect(response.statusCode).to.be.oneOf([200, 204])
},
callback
)
}
function expectSettingsWriteAccess(user, projectId, callback) {
trySettingsWriteAccess(
user,
projectId,
(response, body) => expect(response.statusCode).to.be.oneOf([200, 204]),
callback
)
}
function expectProjectAdminAccess(user, projectId, callback) {
tryProjectAdminAccess(
user,
projectId,
(response, body) => expect(response.statusCode).to.be.oneOf([200, 204]),
callback
)
}
function expectNoReadAccess(user, projectId, callback) {
async.series(
[
cb =>
tryReadAccess(user, projectId, expectErrorResponse.restricted.html, cb),
cb =>
tryContentAccess(
user,
projectId,
(response, body) => {
expect(response.statusCode).to.equal(403)
expect(body).to.equal('Forbidden')
},
cb
),
],
callback
)
}
function expectNoContentWriteAccess(user, projectId, callback) {
tryContentAccess(
user,
projectId,
(response, body) =>
expect(body.privilegeLevel).to.be.oneOf([undefined, null, 'readOnly']),
callback
)
}
function expectNoSettingsWriteAccess(user, projectId, callback) {
trySettingsWriteAccess(
user,
projectId,
expectErrorResponse.restricted.json,
callback
)
}
function expectNoRenameProjectAccess(user, projectId, callback) {
tryRenameProjectAccess(
user,
projectId,
expectErrorResponse.restricted.json,
callback
)
}
function expectNoProjectAdminAccess(user, projectId, callback) {
tryProjectAdminAccess(
user,
projectId,
(response, body) => {
expect(response.statusCode).to.equal(403)
},
callback
)
}
function expectNoAnonymousProjectAdminAccess(user, projectId, callback) {
tryProjectAdminAccess(
user,
projectId,
expectErrorResponse.requireLogin.json,
callback
)
}
function expectChatAccess(user, projectId, callback) {
user.request.get(
{
url: `/project/${projectId}/messages`,
json: true,
},
(error, response) => {
if (error != null) {
return callback(error)
}
expect(response.statusCode).to.equal(200)
callback()
}
)
}
function expectNoChatAccess(user, projectId, callback) {
user.request.get(
{
url: `/project/${projectId}/messages`,
json: true,
},
(error, response) => {
if (error != null) {
return callback(error)
}
expect(response.statusCode).to.equal(403)
callback()
}
)
}
describe('Authorization', function () {
beforeEach(function (done) {
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' })
async.parallel(
[
cb => this.owner.login(cb),
cb => this.other1.login(cb),
cb => this.other2.login(cb),
cb => this.anon.getCsrfToken(cb),
cb => {
this.site_admin.ensureUserExists(err => {
if (err) return cb(err)
this.site_admin.ensureAdmin(err => {
if (err != null) {
return cb(err)
}
return this.site_admin.login(cb)
})
})
},
],
done
)
})
describe('private project', function () {
beforeEach(function (done) {
this.owner.createProject('private-project', (error, projectId) => {
if (error != null) {
return done(error)
}
this.projectId = projectId
done()
})
})
it('should allow the owner read access to it', function (done) {
expectReadAccess(this.owner, this.projectId, done)
})
it('should allow the owner write access to its content', function (done) {
expectContentWriteAccess(this.owner, this.projectId, done)
})
it('should allow the owner write access to its settings', function (done) {
expectSettingsWriteAccess(this.owner, this.projectId, done)
})
it('should allow the owner to rename the project', function (done) {
expectRenameProjectAccess(this.owner, this.projectId, done)
})
it('should allow the owner project admin access to it', function (done) {
expectProjectAdminAccess(this.owner, this.projectId, done)
})
it('should allow the owner user chat messages access', function (done) {
expectChatAccess(this.owner, this.projectId, done)
})
it('should not allow another user read access to the project', function (done) {
expectNoReadAccess(this.other1, this.projectId, done)
})
it('should not allow another user write access to its content', function (done) {
expectNoContentWriteAccess(this.other1, this.projectId, done)
})
it('should not allow another user write access to its settings', function (done) {
expectNoSettingsWriteAccess(this.other1, this.projectId, done)
})
it('should not allow another user to rename the project', function (done) {
expectNoRenameProjectAccess(this.other1, this.projectId, done)
})
it('should not allow another user project admin access to it', function (done) {
expectNoProjectAdminAccess(this.other1, this.projectId, done)
})
it('should not allow another user chat messages access', function (done) {
expectNoChatAccess(this.other1, this.projectId, done)
})
it('should not allow anonymous user read access to it', function (done) {
expectNoReadAccess(this.anon, this.projectId, done)
})
it('should not allow anonymous user write access to its content', function (done) {
expectNoContentWriteAccess(this.anon, this.projectId, done)
})
it('should not allow anonymous user write access to its settings', function (done) {
expectNoSettingsWriteAccess(this.anon, this.projectId, done)
})
it('should not allow anonymous user to rename the project', function (done) {
expectNoRenameProjectAccess(this.anon, this.projectId, done)
})
it('should not allow anonymous user project admin access to it', function (done) {
expectNoAnonymousProjectAdminAccess(this.anon, this.projectId, done)
})
it('should not allow anonymous user chat messages access', function (done) {
expectNoChatAccess(this.anon, this.projectId, done)
})
describe('with admin privilege available', function () {
beforeEach(function () {
settings.adminPrivilegeAvailable = true
})
it('should allow site admin users read access to it', function (done) {
expectReadAccess(this.site_admin, this.projectId, done)
})
it('should allow site admin users write access to its content', function (done) {
expectContentWriteAccess(this.site_admin, this.projectId, done)
})
it('should allow site admin users write access to its settings', function (done) {
expectSettingsWriteAccess(this.site_admin, this.projectId, done)
})
it('should allow site admin users to rename the project', function (done) {
expectRenameProjectAccess(this.site_admin, this.projectId, done)
})
it('should allow site admin users project admin access to it', function (done) {
expectProjectAdminAccess(this.site_admin, this.projectId, done)
})
it('should allow site admin users site admin access to site admin endpoints', function (done) {
expectAdminAccess(this.site_admin, done)
})
})
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', function (done) {
expectNoReadAccess(this.site_admin, this.projectId, done)
})
it('should not allow site admin users write access to its content', function (done) {
expectNoContentWriteAccess(this.site_admin, this.projectId, done)
})
it('should not allow site admin users write access to its settings', function (done) {
expectNoSettingsWriteAccess(this.site_admin, this.projectId, done)
})
it('should not allow site admin users to rename the project', function (done) {
expectNoRenameProjectAccess(this.site_admin, this.projectId, done)
})
it('should not allow site admin users project admin access to it', function (done) {
expectNoProjectAdminAccess(this.site_admin, this.projectId, done)
})
it('should redirect site admin users when accessing site admin endpoints', function (done) {
expectRedirectedAdminAccess(this.site_admin, done)
})
})
})
describe('shared project', function () {
beforeEach(function (done) {
this.rw_user = this.other1
this.ro_user = this.other2
this.owner.createProject('private-project', (error, projectId) => {
if (error != null) {
return done(error)
}
this.projectId = projectId
this.owner.addUserToProject(
this.projectId,
this.ro_user,
'readOnly',
error => {
if (error != null) {
return done(error)
}
this.owner.addUserToProject(
this.projectId,
this.rw_user,
'readAndWrite',
error => {
if (error != null) {
return done(error)
}
done()
}
)
}
)
})
})
it('should allow the read-only user read access to it', function (done) {
expectReadAccess(this.ro_user, this.projectId, done)
})
it('should allow the read-only user chat messages access', function (done) {
expectChatAccess(this.ro_user, this.projectId, done)
})
it('should not allow the read-only user write access to its content', function (done) {
expectNoContentWriteAccess(this.ro_user, this.projectId, done)
})
it('should not allow the read-only user write access to its settings', function (done) {
expectNoSettingsWriteAccess(this.ro_user, this.projectId, done)
})
it('should not allow the read-only user to rename the project', function (done) {
expectNoRenameProjectAccess(this.ro_user, this.projectId, done)
})
it('should not allow the read-only user project admin access to it', function (done) {
expectNoProjectAdminAccess(this.ro_user, this.projectId, done)
})
it('should allow the read-write user read access to it', function (done) {
expectReadAccess(this.rw_user, this.projectId, done)
})
it('should allow the read-write user write access to its content', function (done) {
expectContentWriteAccess(this.rw_user, this.projectId, done)
})
it('should allow the read-write user write access to its settings', function (done) {
expectSettingsWriteAccess(this.rw_user, this.projectId, done)
})
it('should not allow the read-write user to rename the project', function (done) {
expectNoRenameProjectAccess(this.rw_user, this.projectId, done)
})
it('should not allow the read-write user project admin access to it', function (done) {
expectNoProjectAdminAccess(this.rw_user, this.projectId, done)
})
it('should allow the read-write user chat messages access', function (done) {
expectChatAccess(this.rw_user, this.projectId, done)
})
})
describe('public read-write project', function () {
beforeEach(function (done) {
this.owner.createProject('public-rw-project', (error, projectId) => {
if (error != null) {
return done(error)
}
this.projectId = projectId
this.owner.makePublic(this.projectId, 'readAndWrite', done)
})
})
it('should allow a user read access to it', function (done) {
expectReadAccess(this.other1, this.projectId, done)
})
it('should allow a user write access to its content', function (done) {
expectContentWriteAccess(this.other1, this.projectId, done)
})
it('should allow a user chat messages access', function (done) {
expectChatAccess(this.other1, this.projectId, done)
})
it('should not allow a user write access to its settings', function (done) {
expectNoSettingsWriteAccess(this.other1, this.projectId, done)
})
it('should not allow a user to rename the project', function (done) {
expectNoRenameProjectAccess(this.other1, this.projectId, done)
})
it('should not allow a user project admin access to it', function (done) {
expectNoProjectAdminAccess(this.other1, this.projectId, done)
})
it('should allow an anonymous user read access to it', function (done) {
expectReadAccess(this.anon, this.projectId, done)
})
it('should allow an anonymous user write access to its content', function (done) {
expectContentWriteAccess(this.anon, this.projectId, done)
})
it('should allow an anonymous user chat messages access', function (done) {
expectChatAccess(this.anon, this.projectId, done)
})
it('should not allow an anonymous user write access to its settings', function (done) {
expectNoSettingsWriteAccess(this.anon, this.projectId, done)
})
it('should not allow an anonymous user to rename the project', function (done) {
expectNoRenameProjectAccess(this.anon, this.projectId, done)
})
it('should not allow an anonymous user project admin access to it', function (done) {
expectNoAnonymousProjectAdminAccess(this.anon, this.projectId, done)
})
})
describe('public read-only project', function () {
beforeEach(function (done) {
this.owner.createProject('public-ro-project', (error, projectId) => {
if (error != null) {
return done(error)
}
this.projectId = projectId
this.owner.makePublic(this.projectId, 'readOnly', done)
})
})
it('should allow a user read access to it', function (done) {
expectReadAccess(this.other1, this.projectId, done)
})
it('should not allow a user write access to its content', function (done) {
expectNoContentWriteAccess(this.other1, this.projectId, done)
})
it('should not allow a user write access to its settings', function (done) {
expectNoSettingsWriteAccess(this.other1, this.projectId, done)
})
it('should not allow a user to rename the project', function (done) {
expectNoRenameProjectAccess(this.other1, this.projectId, done)
})
it('should not allow a user project admin access to it', function (done) {
expectNoProjectAdminAccess(this.other1, this.projectId, done)
})
// NOTE: legacy readOnly access does not count as 'restricted' in the new model
it('should allow a user chat messages access', function (done) {
expectChatAccess(this.other1, this.projectId, done)
})
it('should allow an anonymous user read access to it', function (done) {
expectReadAccess(this.anon, this.projectId, done)
})
it('should not allow an anonymous user write access to its content', function (done) {
expectNoContentWriteAccess(this.anon, this.projectId, done)
})
it('should not allow an anonymous user write access to its settings', function (done) {
expectNoSettingsWriteAccess(this.anon, this.projectId, done)
})
it('should not allow an anonymous user to rename the project', function (done) {
expectNoRenameProjectAccess(this.anon, this.projectId, done)
})
it('should not allow an anonymous user project admin access to it', function (done) {
expectNoAnonymousProjectAdminAccess(this.anon, this.projectId, done)
})
it('should not allow an anonymous user chat messages access', function (done) {
expectNoChatAccess(this.anon, this.projectId, done)
})
})
})