overleaf/services/web/test/acceptance/src/helpers/User.js
Jessica Lawshe aee83bc0cf Merge pull request #3173 from overleaf/jel-institution-sso-audit-log
Update audit log when institution SSO is linked/unlinked

GitOrigin-RevId: 264ffbed025dccb4dd202f86afe545c3bf0c1f76
2020-09-15 02:04:24 +00:00

743 lines
17 KiB
JavaScript

const request = require('./request')
const settings = require('settings-sharelatex')
const { db, ObjectId } = require('../../../../app/src/infrastructure/mongojs')
const UserModel = require('../../../../app/src/models/User').User
const UserUpdater = require('../../../../app/src/Features/User/UserUpdater')
const AuthenticationManager = require('../../../../app/src/Features/Authentication/AuthenticationManager')
const { promisify } = require('util')
let count = 0
class User {
constructor(options) {
if (options == null) {
options = {}
}
this.emails = [
{
email: options.email || `acceptance-test-${count}@example.com`,
createdAt: new Date()
}
]
this.email = this.emails[0].email
this.password = `acceptance-test-${count}-password`
count++
this.jar = request.jar()
this.request = request.defaults({
jar: this.jar
})
}
setExtraAttributes(user) {
if ((user != null ? user._id : undefined) == null) {
throw new Error('User does not exist')
}
this.id = user._id.toString()
this._id = user._id.toString()
this.first_name = user.first_name
this.referal_id = user.referal_id
}
get(callback) {
db.users.findOne({ _id: ObjectId(this._id) }, callback)
}
mongoUpdate(updateOp, callback) {
db.users.update({ _id: ObjectId(this._id) }, updateOp, callback)
}
register(callback) {
this.registerWithQuery('', callback)
}
registerWithQuery(query, callback) {
if (this._id != null) {
return callback(new Error('User already registered'))
}
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: `/register${query}`,
json: { email: this.email, password: this.password }
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
db.users.findOne({ email: this.email }, (error, user) => {
if (error != null) {
return callback(error)
}
this.setExtraAttributes(user)
callback(null, user)
})
}
)
})
}
login(callback) {
this.loginWith(this.email, callback)
}
loginWith(email, callback) {
this.ensureUserExists(error => {
if (error != null) {
return callback(error)
}
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: settings.enableLegacyLogin ? '/login/legacy' : '/login',
json: { email, password: this.password }
},
callback
)
})
})
}
ensureUserExists(callback) {
const filter = { email: this.email }
const options = { upsert: true, new: true, setDefaultsOnInsert: true }
UserModel.findOneAndUpdate(filter, {}, options, (error, user) => {
if (error != null) {
return callback(error)
}
AuthenticationManager.setUserPasswordInV2(
user._id,
this.password,
error => {
if (error != null) {
return callback(error)
}
UserUpdater.updateUser(
user._id,
{ $set: { emails: this.emails } },
error => {
if (error != null) {
return callback(error)
}
this.setExtraAttributes(user)
callback(null, this.password)
}
)
}
)
})
}
setFeatures(features, callback) {
const update = {}
for (let key in features) {
const value = features[key]
update[`features.${key}`] = value
}
UserModel.update({ _id: this.id }, update, callback)
}
setFeaturesOverride(featuresOverride, callback) {
const update = { $push: { featuresOverrides: featuresOverride } }
UserModel.update({ _id: this.id }, update, callback)
}
setOverleafId(overleafId, callback) {
UserModel.update({ _id: this.id }, { 'overleaf.id': overleafId }, callback)
}
logout(callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/logout',
json: {
email: this.email,
password: this.password
}
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
db.users.findOne({ email: this.email }, (error, user) => {
if (error != null) {
return callback(error)
}
if (user == null) {
return callback()
}
this.id = user._id.toString()
this._id = user._id.toString()
callback()
})
}
)
})
}
addEmail(email, callback) {
this.emails.push({ email, createdAt: new Date() })
UserUpdater.addEmailAddress(
this.id,
email,
{},
{ initiatorId: this._id, ipAddress: '127:0:0:0' },
callback
)
}
confirmEmail(email, callback) {
for (let idx = 0; idx < this.emails.length; idx++) {
const emailData = this.emails[idx]
if (emailData.email === email) {
this.emails[idx].confirmedAt = new Date()
}
}
UserUpdater.confirmEmail(this.id, email, callback)
}
ensureAdmin(callback) {
db.users.update(
{ _id: ObjectId(this.id) },
{ $set: { isAdmin: true } },
callback
)
}
ensureStaffAccess(flag, callback) {
const update = { $set: {} }
update.$set[`staffAccess.${flag}`] = true
db.users.update({ _id: ObjectId(this.id) }, update, callback)
}
upgradeFeatures(callback) {
const features = {
collaborators: -1, // Infinite
versioning: true,
dropbox: true,
compileTimeout: 60,
compileGroup: 'priority',
templates: true,
references: true,
trackChanges: true,
trackChangesVisible: true
}
db.users.update(
{ _id: ObjectId(this.id) },
{ $set: { features } },
callback
)
}
downgradeFeatures(callback) {
const features = {
collaborators: 1,
versioning: false,
dropbox: false,
compileTimeout: 60,
compileGroup: 'standard',
templates: false,
references: false,
trackChanges: false,
trackChangesVisible: false
}
db.users.update(
{ _id: ObjectId(this.id) },
{ $set: { features } },
callback
)
}
defaultFeatures(callback) {
const features = settings.defaultFeatures
db.users.update(
{ _id: ObjectId(this.id) },
{ $set: { features } },
callback
)
}
getFeatures(callback) {
db.users.findOne(
{ _id: ObjectId(this.id) },
{ features: 1 },
(error, user) => callback(error, user && user.features)
)
}
fullDeleteUser(email, callback) {
db.users.findOne({ email }, (error, user) => {
if (error != null) {
return callback(error)
}
if (user == null) {
return callback()
}
const userId = user._id
db.projects.remove(
{ owner_ref: ObjectId(userId) },
{ multi: true },
err => {
if (err != null) {
callback(err)
}
db.users.remove({ _id: ObjectId(userId) }, callback)
}
)
})
}
deleteUser(callback) {
this.getCsrfToken(error => {
if (error) {
return callback(error)
}
this.request.post(
{
url: '/user/delete',
json: { password: this.password }
},
(err, res) => {
if (err) {
return callback(err)
}
if (res.statusCode < 200 || res.statusCode >= 300) {
return callback(
new Error('Error received from API: ' + res.statusCode)
)
}
callback()
}
)
})
}
getProject(projectId, callback) {
db.projects.findOne({ _id: ObjectId(projectId.toString()) }, callback)
}
saveProject(project, callback) {
db.projects.update({ _id: project._id }, project, callback)
}
createProject(name, options, callback) {
if (typeof options === 'function') {
callback = options
options = {}
}
this.request.post(
{
url: '/project/new',
json: Object.assign({ projectName: name }, options)
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
if ((body != null ? body.project_id : undefined) == null) {
error = new Error(
JSON.stringify([
'SOMETHING WENT WRONG CREATING PROJECT',
name,
options,
response.statusCode,
response.headers['location'],
body
])
)
callback(error)
} else {
callback(null, body.project_id)
}
}
)
}
deleteProject(projectId, callback) {
this.request.delete(
{
url: `/project/${projectId}`
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
callback(null)
}
)
}
deleteProjects(callback) {
db.projects.remove({ owner_ref: ObjectId(this.id) }, { multi: true }, err =>
callback(err)
)
}
openProject(projectId, callback) {
this.request.get(
{
url: `/project/${projectId}`
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
if (response.statusCode !== 200) {
const err = new Error(
`Non-success response when opening project: ${response.statusCode}`
)
return callback(err)
}
callback(null)
}
)
}
createDocInProject(projectId, parentFolderId, name, callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: `/project/${projectId}/doc`,
json: {
name,
parentFolderId
}
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
callback(null, body._id)
}
)
})
}
addUserToProject(projectId, user, privileges, callback) {
let updateOp
if (privileges === 'readAndWrite') {
updateOp = { $addToSet: { collaberator_refs: user._id } }
} else if (privileges === 'readOnly') {
updateOp = { $addToSet: { readOnly_refs: user._id } }
}
db.projects.update({ _id: db.ObjectId(projectId) }, updateOp, err =>
callback(err)
)
}
makePublic(projectId, level, callback) {
this.request.post(
{
url: `/project/${projectId}/settings/admin`,
json: {
publicAccessLevel: level
}
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
callback(null)
}
)
}
makePrivate(projectId, callback) {
this.request.post(
{
url: `/project/${projectId}/settings/admin`,
json: {
publicAccessLevel: 'private'
}
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
callback(null)
}
)
}
makeTokenBased(projectId, callback) {
this.request.post(
{
url: `/project/${projectId}/settings/admin`,
json: {
publicAccessLevel: 'tokenBased'
}
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
callback(null)
}
)
}
getCsrfToken(callback) {
this.request.get(
{
url: '/dev/csrf'
},
(err, response, body) => {
if (err != null) {
return callback(err)
}
this.csrfToken = body
this.request = this.request.defaults({
headers: {
'x-csrf-token': this.csrfToken
}
})
callback()
}
)
}
changePassword(callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/user/password/update',
json: {
currentPassword: this.password,
newPassword1: this.password,
newPassword2: this.password
}
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
db.users.findOne({ email: this.email }, (error, user) => {
if (error != null) {
return callback(error)
}
callback()
})
}
)
})
}
reconfirmAccountRequest(userEmail, callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/user/reconfirm',
json: {
email: userEmail
}
},
(error, response, body) => {
callback(error, response)
}
)
})
}
getUserSettingsPage(callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.get(
{
url: '/user/settings'
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
callback(null, response.statusCode)
}
)
})
}
activateSudoMode(callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
uri: '/confirm-password',
json: {
password: this.password
}
},
callback
)
})
}
updateSettings(newSettings, callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/user/settings',
json: newSettings
},
callback
)
})
}
getProjectListPage(callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.get(
{
url: '/project'
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
callback(null, response.statusCode)
}
)
})
}
isLoggedIn(callback) {
this.request.get('/user/personal_info', (error, response, body) => {
if (error != null) {
return callback(error)
}
if (response.statusCode === 200) {
callback(null, true)
} else if (response.statusCode === 302) {
callback(null, false)
} else {
callback(
new Error(
`unexpected status code from /user/personal_info: ${
response.statusCode
}`
)
)
}
})
}
transferProjectOwnership(projectId, userId, callback) {
this.getCsrfToken(err => {
if (err != null) {
return callback(err)
}
this.request.post(
{
url: `/project/${projectId.toString()}/transfer-ownership`,
json: {
user_id: userId.toString()
}
},
(err, response) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 204) {
return callback(
new Error(`Unexpected status code: ${response.statusCode}`)
)
}
callback()
}
)
})
}
setV1Id(v1Id, callback) {
UserModel.update(
{
_id: this._id
},
{
overleaf: {
id: v1Id
}
},
callback
)
}
setCollaboratorInfo(projectId, userId, info, callback) {
this.getCsrfToken(err => {
if (err != null) {
return callback(err)
}
this.request.put(
{
url: `/project/${projectId.toString()}/users/${userId.toString()}`,
json: info
},
(err, response) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 204) {
return callback(
new Error(`Unexpected status code: ${response.statusCode}`)
)
}
callback()
}
)
})
}
}
User.promises = class extends User {
doRequest(method, params) {
return new Promise((resolve, reject) => {
this.request[method.toLowerCase()](params, (err, response, body) => {
if (err) {
reject(err)
} else {
resolve({ response, body })
}
})
})
}
}
// promisify User class methods - works for methods with 0-1 output parameters,
// otherwise we will need to implement the method manually instead
const nonPromiseMethods = ['constructor', 'setExtraAttributes']
Object.getOwnPropertyNames(User.prototype).forEach(methodName => {
const method = User.prototype[methodName]
if (typeof method === 'function' && !nonPromiseMethods.includes(methodName)) {
User.promises.prototype[methodName] = promisify(method)
}
})
module.exports = User