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

975 lines
26 KiB
JavaScript
Raw Normal View History

/* eslint-disable
camelcase,
handle-callback-err,
max-len,
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { expect } = require('chai')
const async = require('async')
const MockV1Api = require('./helpers/MockV1Api')
const User = require('./helpers/User')
const request = require('./helpers/request')
const settings = require('settings-sharelatex')
const { db, ObjectId } = require('../../../app/src/infrastructure/mongojs')
const try_read_access = (user, project_id, test, callback) =>
async.series(
[
cb =>
user.request.get(`/project/${project_id}`, (error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
return cb()
}),
cb =>
user.request.get(
`/project/${project_id}/download/zip`,
(error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
return cb()
}
)
],
callback
)
const try_read_only_token_access = (user, token, test, callback) =>
async.series(
[
cb =>
user.request.get(`/read/${token}`, (error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
return cb()
})
],
callback
)
const try_read_and_write_token_access = (user, token, test, callback) =>
async.series(
[
cb =>
user.request.get(`/${token}`, (error, response, body) => {
if (error != null) {
return cb(error)
}
test(response, body)
return cb()
})
],
callback
)
const try_content_access = function(user, project_id, test, callback) {
// The real-time service calls this end point to determine the user's
// permissions.
let user_id
if (user.id != null) {
user_id = user.id
} else {
user_id = 'anonymous-user'
}
return request.post(
{
url: `/project/${project_id}/join`,
qs: { user_id },
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)
return callback()
}
)
}
const try_anon_content_access = function(
user,
project_id,
token,
test,
callback
) {
// The real-time service calls this end point to determine the user's
// permissions.
let user_id
if (user.id != null) {
user_id = user.id
} else {
user_id = 'anonymous-user'
}
return request.post(
{
url: `/project/${project_id}/join`,
qs: { user_id },
auth: {
user: settings.apis.web.user,
pass: settings.apis.web.pass,
sendImmediately: true
},
headers: {
'x-sl-anonymous-access-token': token
},
json: true,
jar: false
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
test(response, body)
return callback()
}
)
}
describe('TokenAccess', function() {
before(function(done) {
this.timeout(90000)
this.owner = new User()
this.other1 = new User()
this.other2 = new User()
this.anon = new User()
return async.parallel(
[
cb => this.owner.login(cb),
cb => this.other1.login(cb),
cb => this.other2.login(cb),
cb => this.anon.getCsrfToken(cb)
],
done
)
})
describe('no token-access', function() {
before(function(done) {
return this.owner.createProject(
`token-ro-test${Math.random()}`,
(err, project_id) => {
if (err != null) {
return done(err)
}
this.project_id = project_id
// Note, never made token-based,
// thus no tokens
return done()
}
)
})
it('should deny access ', function(done) {
return try_read_access(
this.other1,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should not allow the user to join the project', function(done) {
return try_content_access(
this.other1,
this.project_id,
(response, body) => {
return expect(body.privilegeLevel).to.equal(false)
},
done
)
})
})
describe('read-only token', function() {
before(function(done) {
return this.owner.createProject(
`token-ro-test${Math.random()}`,
(err, project_id) => {
if (err != null) {
return done(err)
}
this.project_id = project_id
return this.owner.makeTokenBased(this.project_id, err => {
if (err != null) {
return done(err)
}
return this.owner.getProject(this.project_id, (err, project) => {
if (err != null) {
return done(err)
}
this.tokens = project.tokens
return done()
})
})
}
)
})
it('should deny access before the token is used', function(done) {
return try_read_access(
this.other1,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should allow the user to access project via read-only token url', function(done) {
return try_read_only_token_access(
this.other1,
this.tokens.readOnly,
(response, body) => {
return expect(response.statusCode).to.equal(200)
},
done
)
})
it('should allow the user to join the project with read-only access', function(done) {
return try_content_access(
this.other1,
this.project_id,
(response, body) => {
return expect(body.privilegeLevel).to.equal('readOnly')
},
done
)
})
describe('made private again', function() {
before(function(done) {
return this.owner.makePrivate(this.project_id, () =>
setTimeout(done, 1000)
)
})
it('should deny access to project', function(done) {
return try_read_access(
this.other1,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should not allow the user to access read-only token', function(done) {
return try_read_only_token_access(
this.other1,
this.tokens.readOnly,
(response, body) => {
return expect(response.statusCode).to.equal(404)
},
done
)
})
it('should not allow the user to join the project', function(done) {
return try_content_access(
this.other1,
this.project_id,
(response, body) => {
return expect(body.privilegeLevel).to.equal(false)
},
done
)
})
})
})
describe('anonymous read-only token', function() {
before(function(done) {
return this.owner.createProject(
`token-anon-ro-test${Math.random()}`,
(err, project_id) => {
if (err != null) {
return done(err)
}
this.project_id = project_id
return this.owner.makeTokenBased(this.project_id, err => {
if (err != null) {
return done(err)
}
return this.owner.getProject(this.project_id, (err, project) => {
if (err != null) {
return done(err)
}
this.tokens = project.tokens
return done()
})
})
}
)
})
it('should deny access before the token is used', function(done) {
return try_read_access(
this.anon,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should allow the user to access project via read-only token url', function(done) {
return try_read_only_token_access(
this.anon,
this.tokens.readOnly,
(response, body) => {
return expect(response.statusCode).to.equal(200)
},
done
)
})
it('should allow the user to anonymously join the project with read-only access', function(done) {
return try_anon_content_access(
this.anon,
this.project_id,
this.tokens.readOnly,
(response, body) => {
return expect(body.privilegeLevel).to.equal('readOnly')
},
done
)
})
describe('made private again', function() {
before(function(done) {
return this.owner.makePrivate(this.project_id, () =>
setTimeout(done, 1000)
)
})
it('should deny access to project', function(done) {
return try_read_access(
this.anon,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should not allow the user to access read-only token', function(done) {
return try_read_only_token_access(
this.anon,
this.tokens.readOnly,
(response, body) => {
return expect(response.statusCode).to.equal(404)
},
done
)
})
it('should not allow the user to join the project', function(done) {
return try_anon_content_access(
this.anon,
this.project_id,
this.tokens.readOnly,
(response, body) => {
return expect(body.privilegeLevel).to.equal(false)
},
done
)
})
})
})
describe('read-and-write token', function() {
before(function(done) {
return this.owner.createProject(
`token-rw-test${Math.random()}`,
(err, project_id) => {
if (err != null) {
return done(err)
}
this.project_id = project_id
return this.owner.makeTokenBased(this.project_id, err => {
if (err != null) {
return done(err)
}
return this.owner.getProject(this.project_id, (err, project) => {
if (err != null) {
return done(err)
}
this.tokens = project.tokens
return done()
})
})
}
)
})
it('should deny access before the token is used', function(done) {
return try_read_access(
this.other1,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
expect(response.headers.location).to.match(/\/restricted.*/)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should allow the user to access project via read-and-write token url', function(done) {
return try_read_and_write_token_access(
this.other1,
this.tokens.readAndWrite,
(response, body) => {
return expect(response.statusCode).to.equal(200)
},
done
)
})
it('should allow the user to join the project with read-and-write access', function(done) {
return try_content_access(
this.other1,
this.project_id,
(response, body) => {
return expect(body.privilegeLevel).to.equal('readAndWrite')
},
done
)
})
describe('made private again', function() {
before(function(done) {
return this.owner.makePrivate(this.project_id, () =>
setTimeout(done, 1000)
)
})
it('should deny access to project', function(done) {
return try_read_access(
this.other1,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should not allow the user to access read-and-write token', function(done) {
return try_read_and_write_token_access(
this.other1,
this.tokens.readAndWrite,
(response, body) => {
return expect(response.statusCode).to.equal(404)
},
done
)
})
it('should not allow the user to join the project', function(done) {
return try_content_access(
this.other1,
this.project_id,
(response, body) => {
return expect(body.privilegeLevel).to.equal(false)
},
done
)
})
})
})
if (!settings.allowAnonymousReadAndWriteSharing) {
describe('anonymous read-and-write token, disabled', function() {
before(function(done) {
return this.owner.createProject(
`token-anon-rw-test${Math.random()}`,
(err, project_id) => {
if (err != null) {
return done(err)
}
this.project_id = project_id
return this.owner.makeTokenBased(this.project_id, err => {
if (err != null) {
return done(err)
}
return this.owner.getProject(this.project_id, (err, project) => {
if (err != null) {
return done(err)
}
this.tokens = project.tokens
return done()
})
})
}
)
})
it('should deny access before the token is used', function(done) {
return try_read_access(
this.anon,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should not allow the user to access read-and-write token', function(done) {
return try_read_and_write_token_access(
this.anon,
this.tokens.readAndWrite,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should not allow the user to join the project', function(done) {
return try_anon_content_access(
this.anon,
this.project_id,
this.tokens.readAndWrite,
(response, body) => {
return expect(body.privilegeLevel).to.equal(false)
},
done
)
})
})
} else {
describe('anonymous read-and-write token, enabled', function() {
before(function(done) {
return this.owner.createProject(
`token-anon-rw-test${Math.random()}`,
(err, project_id) => {
if (err != null) {
return done(err)
}
this.project_id = project_id
return this.owner.makeTokenBased(this.project_id, err => {
if (err != null) {
return done(err)
}
return this.owner.getProject(this.project_id, (err, project) => {
if (err != null) {
return done(err)
}
this.tokens = project.tokens
return done()
})
})
}
)
})
it('should deny access before the token is used', function(done) {
return try_read_access(
this.anon,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should allow the user to access project via read-and-write token url', function(done) {
return try_read_and_write_token_access(
this.anon,
this.tokens.readAndWrite,
(response, body) => {
return expect(response.statusCode).to.equal(200)
},
done
)
})
it('should allow the user to anonymously join the project with read-and-write access', function(done) {
return try_anon_content_access(
this.anon,
this.project_id,
this.tokens.readAndWrite,
(response, body) => {
return expect(body.privilegeLevel).to.equal('readAndWrite')
},
done
)
})
describe('made private again', function() {
before(function(done) {
return this.owner.makePrivate(this.project_id, () =>
setTimeout(done, 1000)
)
})
it('should deny access to project', function(done) {
return try_read_access(
this.anon,
this.project_id,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(body).to.match(/.*\/restricted.*/)
},
done
)
})
it('should not allow the user to access read-and-write token', function(done) {
return try_read_and_write_token_access(
this.anon,
this.tokens.readAndWrite,
(response, body) => {
return expect(response.statusCode).to.equal(404)
},
done
)
})
it('should not allow the user to join the project', function(done) {
return try_anon_content_access(
this.anon,
this.project_id,
this.tokens.readAndWrite,
(response, body) => {
return expect(body.privilegeLevel).to.equal(false)
},
done
)
})
})
})
}
describe('private overleaf project', function() {
before(function(done) {
return this.owner.createProject('overleaf-import', (err, project_id) => {
this.project_id = project_id
return this.owner.makeTokenBased(this.project_id, err => {
return this.owner.getProject(this.project_id, (err, project) => {
this.tokens = project.tokens
return this.owner.makePrivate(this.project_id, () => {
return db.projects.update(
{ _id: project._id },
{
$set: {
overleaf: { id: 1234 }
}
},
err => {
return done()
}
)
})
})
})
})
})
it('should redirect to canonical path, when owner uses read-write token', function(done) {
return try_read_and_write_token_access(
this.owner,
this.tokens.readAndWrite,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(response.headers.location).to.equal(
`/project/${this.project_id}`
)
},
done
)
})
it('should allow the owner access to the project', function(done) {
return try_read_access(
this.owner,
this.project_id,
(response, body) => {
return expect(response.statusCode).to.equal(200)
},
done
)
})
it('should allow owner to join the project', function(done) {
return try_content_access(
this.owner,
this.project_id,
(response, body) => {
return expect(body.privilegeLevel).to.equal('owner')
},
done
)
})
it('should not allow other user to join the project', function(done) {
return try_content_access(
this.other2,
this.project_id,
(response, body) => {
return expect(body.privilegeLevel).to.equal(false)
},
done
)
})
})
describe('private project, with higher access', function() {
before(function(done) {
return this.owner.createProject(
`higher-access-test-${Math.random()}`,
(err, project_id) => {
this.project_id = project_id
return this.owner.addUserToProject(
this.project_id,
this.other1,
'readAndWrite',
err => {
return this.owner.makeTokenBased(this.project_id, err => {
return this.owner.getProject(
this.project_id,
(err, project) => {
this.tokens = project.tokens
return this.owner.makePrivate(this.project_id, () => {
return setTimeout(done, 1000)
})
}
)
})
}
)
}
)
})
it('should redirect to canonical path, when user uses read-write token', function(done) {
return try_read_and_write_token_access(
this.other1,
this.tokens.readAndWrite,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(response.headers.location).to.equal(
`/project/${this.project_id}`
)
},
done
)
})
it('should redirect to canonical path, when user uses read-only token', function(done) {
return try_read_only_token_access(
this.other1,
this.tokens.readOnly,
(response, body) => {
expect(response.statusCode).to.equal(302)
return expect(response.headers.location).to.equal(
`/project/${this.project_id}`
)
},
done
)
})
it('should allow the user access to the project', function(done) {
return try_read_access(
this.other1,
this.project_id,
(response, body) => {
return expect(response.statusCode).to.equal(200)
},
done
)
})
it('should allow user to join the project', function(done) {
return try_content_access(
this.other1,
this.project_id,
(response, body) => {
return expect(body.privilegeLevel).to.equal('readAndWrite')
},
done
)
})
it('should not allow a different user to join the project', function(done) {
return try_content_access(
this.other2,
this.project_id,
(response, body) => {
return expect(body.privilegeLevel).to.equal(false)
},
done
)
})
})
describe('unimported v1 project', function() {
before(function() {
return (settings.overleaf = { host: 'http://localhost:5000' })
})
after(function() {
return delete settings.overleaf
})
it('should show error page for read and write token', function(done) {
const unimportedV1Token = '123abc'
try_read_and_write_token_access(
this.owner,
unimportedV1Token,
(response, body) => {
expect(response.statusCode).to.equal(400)
},
done
)
})
it('should show error page for read only token to v1', function(done) {
const unimportedV1Token = 'abcd'
try_read_only_token_access(
this.owner,
unimportedV1Token,
(response, body) => {
expect(response.statusCode).to.equal(400)
},
done
)
})
})
describe('importing v1 project', function() {
before(function(done) {
settings.projectImportingCheckMaxCreateDelta = 3600
settings.overleaf = { host: 'http://localhost:5000' }
return this.owner.createProject(
`token-rw-test${Math.random()}`,
(err, project_id) => {
if (err != null) {
return done(err)
}
this.project_id = project_id
return this.owner.makeTokenBased(this.project_id, err => {
if (err != null) {
return done(err)
}
return db.projects.update(
{ _id: ObjectId(project_id) },
{ $set: { overleaf: { id: 1234 } } },
err => {
if (err != null) {
return done(err)
}
return this.owner.getProject(
this.project_id,
(err, project) => {
if (err != null) {
return done(err)
}
this.tokens = project.tokens
MockV1Api.setDocExported(this.tokens.readAndWrite, {
exporting: true
})
MockV1Api.setDocExported(this.tokens.readOnly, {
exporting: true
})
return done()
}
)
}
)
})
}
)
})
after(function() {
delete settings.projectImportingCheckMaxCreateDelta
return delete settings.overleaf
})
it('should show importing page for read and write token', function(done) {
return try_read_and_write_token_access(
this.owner,
this.tokens.readAndWrite,
(response, body) => {
expect(response.statusCode).to.equal(200)
return expect(body).to.include('ImportingController')
},
done
)
})
it('should show importing page for read only token', function(done) {
return try_read_only_token_access(
this.owner,
this.tokens.readOnly,
(response, body) => {
expect(response.statusCode).to.equal(200)
return expect(body).to.include('ImportingController')
},
done
)
})
describe('when importing check not configured', function() {
before(function() {
return delete settings.projectImportingCheckMaxCreateDelta
})
it('should load editor', function(done) {
return try_read_and_write_token_access(
this.owner,
this.tokens.readAndWrite,
(response, body) => {
expect(response.statusCode).to.equal(200)
return expect(body).to.include('IdeController')
},
done
)
})
})
})
})