mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-11 23:20:47 +02:00
[web] add ProjectAccess helper class (#25663)
* [web] add ProjectAccess helper class * [web] remove ts-ignore for calling OError.tag with try/catch error GitOrigin-RevId: e097a95b4d929a3927a3eeb70635590680c93007
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// @ts-check
|
||||
const { callbackify } = require('util')
|
||||
const pLimit = require('p-limit')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
@@ -50,7 +51,155 @@ module.exports = {
|
||||
},
|
||||
}
|
||||
|
||||
async function getMemberIdsWithPrivilegeLevels(projectId) {
|
||||
/**
|
||||
* @typedef ProjectMember
|
||||
* @property {string} id
|
||||
* @property {typeof PrivilegeLevels[keyof PrivilegeLevels]} privilegeLevel
|
||||
* @property {typeof Sources[keyof Sources]} source
|
||||
* @property {boolean} [pendingEditor]
|
||||
* @property {boolean} [pendingReviewer]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef LoadedProjectMember
|
||||
* @property {typeof PrivilegeLevels[keyof PrivilegeLevels]} privilegeLevel
|
||||
* @property {{_id: ObjectId, email: string, features: any, first_name: string, last_name: string, signUpDate: Date}} user
|
||||
* @property {boolean} [pendingEditor]
|
||||
* @property {boolean} [pendingReviewer]
|
||||
*/
|
||||
|
||||
// Wrapper for determining multiple dimensions of project access.
|
||||
class ProjectAccess {
|
||||
/** @type {ProjectMember[]} */
|
||||
#members
|
||||
|
||||
/** @type {typeof PublicAccessLevels[keyof PublicAccessLevels]} */
|
||||
#publicAccessLevel
|
||||
|
||||
/**
|
||||
* @param {{ owner_ref: ObjectId; collaberator_refs: ObjectId[]; readOnly_refs: ObjectId[]; tokenAccessReadAndWrite_refs: ObjectId[]; tokenAccessReadOnly_refs: ObjectId[]; publicAccesLevel: typeof PublicAccessLevels[keyof PublicAccessLevels]; pendingEditor_refs: ObjectId[]; reviewer_refs: ObjectId[]; pendingReviewer_refs: ObjectId[]; }} project
|
||||
*/
|
||||
constructor(project) {
|
||||
this.#members = _getMemberIdsWithPrivilegeLevelsFromFields(
|
||||
project.owner_ref,
|
||||
project.collaberator_refs,
|
||||
project.readOnly_refs,
|
||||
project.tokenAccessReadAndWrite_refs,
|
||||
project.tokenAccessReadOnly_refs,
|
||||
project.publicAccesLevel,
|
||||
project.pendingEditor_refs,
|
||||
project.reviewer_refs,
|
||||
project.pendingReviewer_refs
|
||||
)
|
||||
this.#publicAccessLevel = project.publicAccesLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<LoadedProjectMember[]>}
|
||||
*/
|
||||
async loadInvitedMembers() {
|
||||
return _loadMembers(this.#members.filter(m => m.source !== Sources.TOKEN))
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {ProjectMember[]}
|
||||
*/
|
||||
allMembers() {
|
||||
return this.#members
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {typeof PublicAccessLevels[keyof PublicAccessLevels]}
|
||||
*/
|
||||
publicAccessLevel() {
|
||||
return this.#publicAccessLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string[]}
|
||||
*/
|
||||
memberIds() {
|
||||
return this.#members.map(m => m.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string[]}
|
||||
*/
|
||||
invitedMemberIds() {
|
||||
return this.#members.filter(m => m.source !== Sources.TOKEN).map(m => m.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | ObjectId} userId
|
||||
* @return {typeof PrivilegeLevels[keyof PrivilegeLevels]}
|
||||
*/
|
||||
privilegeLevelForUser(userId) {
|
||||
for (const member of this.#members) {
|
||||
if (member.id === userId.toString()) {
|
||||
return member.privilegeLevel
|
||||
}
|
||||
}
|
||||
return PrivilegeLevels.NONE
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | ObjectId} userId
|
||||
* @return {boolean}
|
||||
*/
|
||||
isUserInvitedMember(userId) {
|
||||
for (const member of this.#members) {
|
||||
if (member.id === userId.toString() && member.source !== Sources.TOKEN) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | ObjectId} userId
|
||||
* @return {boolean}
|
||||
*/
|
||||
isUserInvitedReadWriteMember(userId) {
|
||||
for (const member of this.#members) {
|
||||
if (
|
||||
member.id.toString() === userId.toString() &&
|
||||
member.source !== Sources.TOKEN &&
|
||||
member.privilegeLevel === PrivilegeLevels.READ_AND_WRITE
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts invited members with editor or reviewer roles
|
||||
* @return {number}
|
||||
*/
|
||||
countInvitedEditCollaborators() {
|
||||
return this.#members.filter(
|
||||
m =>
|
||||
m.source === Sources.INVITE &&
|
||||
(m.privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
|
||||
m.privilegeLevel === PrivilegeLevels.REVIEW)
|
||||
).length
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts invited members that are readonly pending editors or pending reviewers
|
||||
* @return {number}
|
||||
*/
|
||||
countInvitedPendingEditors() {
|
||||
return this.#members.filter(
|
||||
m =>
|
||||
m.source === Sources.INVITE &&
|
||||
m.privilegeLevel === PrivilegeLevels.READ_ONLY &&
|
||||
(m.pendingEditor || m.pendingReviewer)
|
||||
).length
|
||||
}
|
||||
}
|
||||
|
||||
async function getProjectAccess(projectId) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
owner_ref: 1,
|
||||
collaberator_refs: 1,
|
||||
@@ -65,34 +214,23 @@ async function getMemberIdsWithPrivilegeLevels(projectId) {
|
||||
if (!project) {
|
||||
throw new Errors.NotFoundError(`no project found with id ${projectId}`)
|
||||
}
|
||||
const memberIds = _getMemberIdsWithPrivilegeLevelsFromFields(
|
||||
project.owner_ref,
|
||||
project.collaberator_refs,
|
||||
project.readOnly_refs,
|
||||
project.tokenAccessReadAndWrite_refs,
|
||||
project.tokenAccessReadOnly_refs,
|
||||
project.publicAccesLevel,
|
||||
project.pendingEditor_refs,
|
||||
project.reviewer_refs,
|
||||
project.pendingReviewer_refs
|
||||
)
|
||||
return memberIds
|
||||
return new ProjectAccess(project)
|
||||
}
|
||||
|
||||
async function getMemberIdsWithPrivilegeLevels(projectId) {
|
||||
return (await getProjectAccess(projectId)).allMembers()
|
||||
}
|
||||
|
||||
async function getMemberIds(projectId) {
|
||||
const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
||||
return members.map(m => m.id)
|
||||
return (await getProjectAccess(projectId)).memberIds()
|
||||
}
|
||||
|
||||
async function getInvitedMemberIds(projectId) {
|
||||
const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
||||
return members.filter(m => m.source !== Sources.TOKEN).map(m => m.id)
|
||||
return (await getProjectAccess(projectId)).invitedMemberIds()
|
||||
}
|
||||
|
||||
async function getInvitedMembersWithPrivilegeLevels(projectId) {
|
||||
let members = await getMemberIdsWithPrivilegeLevels(projectId)
|
||||
members = members.filter(m => m.source !== Sources.TOKEN)
|
||||
return _loadMembers(members)
|
||||
return await (await getProjectAccess(projectId)).loadInvitedMembers()
|
||||
}
|
||||
|
||||
async function getInvitedMembersWithPrivilegeLevelsFromFields(
|
||||
@@ -107,7 +245,7 @@ async function getInvitedMembersWithPrivilegeLevelsFromFields(
|
||||
readOnlyIds,
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
'private',
|
||||
[],
|
||||
reviewerIds,
|
||||
[]
|
||||
@@ -121,69 +259,31 @@ async function getMemberIdPrivilegeLevel(userId, projectId) {
|
||||
if (userId == null) {
|
||||
return PrivilegeLevels.NONE
|
||||
}
|
||||
const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
||||
for (const member of members) {
|
||||
if (member.id === userId.toString()) {
|
||||
return member.privilegeLevel
|
||||
}
|
||||
}
|
||||
return PrivilegeLevels.NONE
|
||||
return (await getProjectAccess(projectId)).privilegeLevelForUser(userId)
|
||||
}
|
||||
|
||||
async function getInvitedEditCollaboratorCount(projectId) {
|
||||
// Counts invited members with editor or reviewer roles
|
||||
const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
||||
return members.filter(
|
||||
m =>
|
||||
m.source === Sources.INVITE &&
|
||||
(m.privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
|
||||
m.privilegeLevel === PrivilegeLevels.REVIEW)
|
||||
).length
|
||||
return (await getProjectAccess(projectId)).countInvitedEditCollaborators()
|
||||
}
|
||||
|
||||
async function getInvitedPendingEditorCount(projectId) {
|
||||
// Only counts invited members that are readonly pending editors or pending
|
||||
// reviewers
|
||||
const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
||||
return members.filter(
|
||||
m =>
|
||||
m.source === Sources.INVITE &&
|
||||
m.privilegeLevel === PrivilegeLevels.READ_ONLY &&
|
||||
(m.pendingEditor || m.pendingReviewer)
|
||||
).length
|
||||
return (await getProjectAccess(projectId)).countInvitedPendingEditors()
|
||||
}
|
||||
|
||||
async function isUserInvitedMemberOfProject(userId, projectId) {
|
||||
if (!userId) {
|
||||
return false
|
||||
}
|
||||
const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
||||
for (const member of members) {
|
||||
if (
|
||||
member.id.toString() === userId.toString() &&
|
||||
member.source !== Sources.TOKEN
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return (await getProjectAccess(projectId)).isUserInvitedMember(userId)
|
||||
}
|
||||
|
||||
async function isUserInvitedReadWriteMemberOfProject(userId, projectId) {
|
||||
if (!userId) {
|
||||
return false
|
||||
}
|
||||
const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
||||
for (const member of members) {
|
||||
if (
|
||||
member.id.toString() === userId.toString() &&
|
||||
member.source !== Sources.TOKEN &&
|
||||
member.privilegeLevel === PrivilegeLevels.READ_AND_WRITE
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return (await getProjectAccess(projectId)).isUserInvitedReadWriteMember(
|
||||
userId
|
||||
)
|
||||
}
|
||||
|
||||
async function getPublicShareTokens(userId, projectId) {
|
||||
@@ -209,10 +309,13 @@ async function getPublicShareTokens(userId, projectId) {
|
||||
return null
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (memberInfo.isOwner) {
|
||||
return memberInfo.tokens
|
||||
// @ts-ignore
|
||||
} else if (memberInfo.hasTokenReadOnlyAccess) {
|
||||
return {
|
||||
// @ts-ignore
|
||||
readOnly: memberInfo.tokens.readOnly,
|
||||
}
|
||||
} else {
|
||||
@@ -224,6 +327,7 @@ async function getPublicShareTokens(userId, projectId) {
|
||||
// excluding projects where the user is listed in the token access fields when
|
||||
// token access has been disabled.
|
||||
async function getProjectsUserIsMemberOf(userId, fields) {
|
||||
// @ts-ignore
|
||||
const limit = pLimit(2)
|
||||
const [readAndWrite, review, readOnly, tokenReadAndWrite, tokenReadOnly] =
|
||||
await Promise.all([
|
||||
@@ -274,9 +378,9 @@ async function dangerouslyGetAllProjectsUserIsMemberOf(userId, fields) {
|
||||
|
||||
async function getAllInvitedMembers(projectId) {
|
||||
try {
|
||||
const rawMembers = await getInvitedMembersWithPrivilegeLevels(projectId)
|
||||
const { members } =
|
||||
ProjectEditorHandler.buildOwnerAndMembersViews(rawMembers)
|
||||
const { members } = ProjectEditorHandler.buildOwnerAndMembersViews(
|
||||
await (await getProjectAccess(projectId)).loadInvitedMembers()
|
||||
)
|
||||
return members
|
||||
} catch (err) {
|
||||
throw OError.tag(err, 'error getting members for project', { projectId })
|
||||
@@ -316,6 +420,19 @@ async function userIsReadWriteTokenMember(userId, projectId) {
|
||||
return project != null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ObjectId} ownerId
|
||||
* @param {ObjectId[]} collaboratorIds
|
||||
* @param {ObjectId[]} readOnlyIds
|
||||
* @param {ObjectId[]} tokenAccessIds
|
||||
* @param {ObjectId[]} tokenAccessReadOnlyIds
|
||||
* @param {typeof PublicAccessLevels[keyof PublicAccessLevels]} publicAccessLevel
|
||||
* @param {ObjectId[]} pendingEditorIds
|
||||
* @param {ObjectId[]} reviewerIds
|
||||
* @param {ObjectId[]} pendingReviewerIds
|
||||
* @return {ProjectMember[]}
|
||||
* @private
|
||||
*/
|
||||
function _getMemberIdsWithPrivilegeLevelsFromFields(
|
||||
ownerId,
|
||||
collaboratorIds,
|
||||
@@ -384,6 +501,11 @@ function _getMemberIdsWithPrivilegeLevelsFromFields(
|
||||
return members
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProjectMember[]} members
|
||||
* @return {Promise<LoadedProjectMember[]>}
|
||||
* @private
|
||||
*/
|
||||
async function _loadMembers(members) {
|
||||
const userIds = Array.from(new Set(members.map(m => m.id)))
|
||||
const users = new Map()
|
||||
|
||||
Reference in New Issue
Block a user