Merge pull request #17023 from overleaf/ii-token-access-null-values
[web] Fix projects token access values GitOrigin-RevId: f0c6a4993e42320c06753cb65198138afe55b71a
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
const { db, waitForDb } = require('../app/src/infrastructure/mongodb')
|
||||
const { batchedUpdate } = require('./helpers/batchedUpdate')
|
||||
const { ObjectId } = require('mongodb')
|
||||
const minimist = require('minimist')
|
||||
const CollaboratorsHandler = require('../app/src/Features/Collaborators/CollaboratorsHandler')
|
||||
|
||||
const argv = minimist(process.argv.slice(2), {
|
||||
string: ['projects'],
|
||||
boolean: ['dry-run', 'help'],
|
||||
alias: {
|
||||
projects: 'p',
|
||||
},
|
||||
default: {
|
||||
'dry-run': true,
|
||||
},
|
||||
})
|
||||
|
||||
if (argv.help || argv._.length > 1) {
|
||||
console.error(`Usage: node scripts/remove_deleted_users_from_token_access_refs.js [OPTS]
|
||||
Finds or removes deleted user ids from token access fields
|
||||
"tokenAccessReadOnly_refs" and "tokenAccessReadAndWrite_refs" in the "projects" collection.
|
||||
|
||||
If no projects are specified, all projects will be processed.
|
||||
|
||||
Options:
|
||||
|
||||
--dry-run finds projects and deleted users but does not do any updates
|
||||
--projects list of projects ids to be fixed (comma separated)
|
||||
`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const DRY_RUN = argv['dry-run']
|
||||
const PROJECTS_LIST = argv.projects
|
||||
|
||||
async function findUserIds() {
|
||||
const userIds = new Set()
|
||||
const cursor = db.users.find({}, { _id: 1 })
|
||||
for await (const user of cursor) {
|
||||
userIds.add(user._id.toString())
|
||||
}
|
||||
console.log(`=> User ids count: ${userIds.size}`)
|
||||
return userIds
|
||||
}
|
||||
|
||||
async function fixProjectsWithInvalidTokenAccessRefsIds(
|
||||
DRY_RUN,
|
||||
PROJECTS_LIST
|
||||
) {
|
||||
if (DRY_RUN) {
|
||||
console.log('=> Doing dry run')
|
||||
}
|
||||
|
||||
const DELETED_USER_COLLABORATOR_IDS = new Set()
|
||||
const PROJECTS_WITH_DELETED_USER = new Set()
|
||||
|
||||
// get a set of all users ids as an in-memory cache
|
||||
const userIds = await findUserIds()
|
||||
|
||||
// default query for finding all projects with non-empty token access fields
|
||||
let query = {
|
||||
$or: [
|
||||
{ 'tokenAccessReadOnly_refs.0': { $exists: true } },
|
||||
{ 'tokenAccessReadAndWrite_refs.0': { $exists: true } },
|
||||
],
|
||||
}
|
||||
|
||||
const projectIds = PROJECTS_LIST?.split(',').map(
|
||||
projectId => new ObjectId(projectId)
|
||||
)
|
||||
|
||||
// query for finding projects passed in as args
|
||||
if (projectIds) {
|
||||
query = { $and: [{ _id: { $in: projectIds } }] }
|
||||
}
|
||||
|
||||
await batchedUpdate(
|
||||
'projects',
|
||||
query,
|
||||
async projects => {
|
||||
for (const project of projects) {
|
||||
// find the set of user ids that are in the token access fields
|
||||
// i.e. the set of collaborators
|
||||
const collaboratorIds = new Set()
|
||||
for (const roUserId of project.tokenAccessReadOnly_refs) {
|
||||
collaboratorIds.add(roUserId.toString())
|
||||
}
|
||||
for (const rwUserId of project.tokenAccessReadAndWrite_refs) {
|
||||
collaboratorIds.add(rwUserId.toString())
|
||||
}
|
||||
// determine which collaborator ids are not in the `users` collection
|
||||
// i.e. the user has been deleted
|
||||
const deletedUserIds = new Set()
|
||||
for (const collaboratorId of collaboratorIds) {
|
||||
if (!userIds.has(collaboratorId)) {
|
||||
deletedUserIds.add(collaboratorId)
|
||||
}
|
||||
}
|
||||
|
||||
// double-check that users doesn't exist in the users collection
|
||||
// we don't want to remove users that were added after the initial query
|
||||
const existingUsersCursor = db.users.find(
|
||||
{ _id: { $in: [...deletedUserIds].map(id => new ObjectId(id)) } },
|
||||
{ _id: 1 }
|
||||
)
|
||||
for await (const user of existingUsersCursor) {
|
||||
const id = user._id.toString()
|
||||
deletedUserIds.delete(id)
|
||||
// add the user id to the cache
|
||||
userIds.add(id)
|
||||
}
|
||||
|
||||
// remove the actual deleted users
|
||||
for (const deletedUserId of deletedUserIds) {
|
||||
DELETED_USER_COLLABORATOR_IDS.add(deletedUserId)
|
||||
PROJECTS_WITH_DELETED_USER.add(project._id.toString())
|
||||
console.log(
|
||||
'=> Found deleted user id:',
|
||||
deletedUserId,
|
||||
'in project:',
|
||||
project._id.toString()
|
||||
)
|
||||
if (DRY_RUN) {
|
||||
console.log(
|
||||
`=> DRY RUN - would remove deleted ${deletedUserId} from all projects (found in project ${project._id.toString()})`
|
||||
)
|
||||
continue
|
||||
}
|
||||
console.log(
|
||||
`=> Removing deleted ${deletedUserId} from all projects (found in project ${project._id.toString()})`
|
||||
)
|
||||
await CollaboratorsHandler.promises.removeUserFromAllProjects(
|
||||
new ObjectId(deletedUserId)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ tokenAccessReadOnly_refs: 1, tokenAccessReadAndWrite_refs: 1 }
|
||||
)
|
||||
|
||||
console.log(
|
||||
`=> ${DRY_RUN ? 'DRY RUN - would delete' : 'Deleted'} user ids (${
|
||||
DELETED_USER_COLLABORATOR_IDS.size
|
||||
})`
|
||||
)
|
||||
if (DELETED_USER_COLLABORATOR_IDS.size) {
|
||||
console.log(Array.from(DELETED_USER_COLLABORATOR_IDS).join('\n'))
|
||||
}
|
||||
console.log(
|
||||
`=> Projects with deleted user ids (${PROJECTS_WITH_DELETED_USER.size})`
|
||||
)
|
||||
if (PROJECTS_WITH_DELETED_USER.size) {
|
||||
console.log(Array.from(PROJECTS_WITH_DELETED_USER).join('\n'))
|
||||
}
|
||||
}
|
||||
|
||||
async function main(DRY_RUN, PROJECTS_LIST) {
|
||||
await waitForDb()
|
||||
await fixProjectsWithInvalidTokenAccessRefsIds(DRY_RUN, PROJECTS_LIST)
|
||||
}
|
||||
|
||||
module.exports = main
|
||||
|
||||
if (require.main === module) {
|
||||
main(DRY_RUN, PROJECTS_LIST)
|
||||
.then(() => {
|
||||
console.error('Done')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user