fix(server): prevent creating project contributor invite to viewer se… (#4462)
* fix(server): prevent creating project contributor invite to viewer seat member * undo regionConfig change * moar cleanup
This commit is contained in:
committed by
GitHub
parent
385157ac81
commit
93bc55630b
@@ -57,7 +57,8 @@ import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/con
|
||||
import {
|
||||
GetWorkspace,
|
||||
GetWorkspaceBySlug,
|
||||
GetWorkspaceDomains
|
||||
GetWorkspaceDomains,
|
||||
ValidateWorkspaceMemberProjectRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import { WorkspaceInviteResourceTarget } from '@/modules/workspaces/domain/types'
|
||||
import { mapGqlWorkspaceRoleToMainRole } from '@/modules/workspaces/helpers/roles'
|
||||
@@ -72,6 +73,7 @@ import {
|
||||
} from '@/modules/workspaces/domain/logic'
|
||||
import { GetStream } from '@/modules/core/domain/streams/operations'
|
||||
import { GetUser } from '@/modules/core/domain/users/operations'
|
||||
import { GetWorkspaceRoleAndSeat } from '@/modules/workspacesCore/domain/operations'
|
||||
|
||||
export const isWorkspaceResourceTarget = (
|
||||
target: InviteResourceTarget
|
||||
@@ -122,6 +124,8 @@ export const createWorkspaceInviteFactory =
|
||||
type CollectAndValidateWorkspaceTargetsFactoryDeps =
|
||||
CollectAndValidateCoreTargetsFactoryDeps & {
|
||||
getWorkspace: GetWorkspace
|
||||
getWorkspaceRoleAndSeat: GetWorkspaceRoleAndSeat
|
||||
validateWorkspaceMemberProjectRoleFactory: ValidateWorkspaceMemberProjectRole
|
||||
getWorkspaceDomains: GetWorkspaceDomains
|
||||
findVerifiedEmailsByUserId: FindVerifiedEmailsByUserId
|
||||
getStream: GetStream
|
||||
@@ -191,31 +195,42 @@ export const collectAndValidateWorkspaceTargetsFactory =
|
||||
return [...baseTargets]
|
||||
}
|
||||
|
||||
const workspace = await deps.getWorkspace({
|
||||
workspaceId,
|
||||
userId: targetUser?.id
|
||||
})
|
||||
const [workspace, workspaceRoleAndSeat] = await Promise.all([
|
||||
deps.getWorkspace({
|
||||
workspaceId
|
||||
}),
|
||||
...(targetUser?.id
|
||||
? [
|
||||
deps.getWorkspaceRoleAndSeat({
|
||||
workspaceId,
|
||||
userId: targetUser.id
|
||||
})
|
||||
]
|
||||
: [])
|
||||
])
|
||||
if (!workspace) {
|
||||
throw new InviteCreateValidationError(
|
||||
'Attempting to invite into a non-existant workspace'
|
||||
)
|
||||
}
|
||||
|
||||
// If inviting to workspace project, disallow workspace guests to become project owners
|
||||
const workspaceRole = workspaceRoleAndSeat?.role.role
|
||||
|
||||
// If inviting to workspace project, validate target role
|
||||
const projectTarget = baseTargets.find(isProjectResourceTarget)
|
||||
if (
|
||||
workspace?.role === Roles.Workspace.Guest &&
|
||||
projectTarget?.role === Roles.Stream.Owner
|
||||
) {
|
||||
throw new InviteCreateValidationError(
|
||||
'Workspace guests cannot be owners of workspace projects'
|
||||
)
|
||||
const projectRole = projectTarget?.role
|
||||
if (projectRole && targetUser) {
|
||||
await deps.validateWorkspaceMemberProjectRoleFactory({
|
||||
workspaceId,
|
||||
userId: targetUser.id,
|
||||
projectRole
|
||||
})
|
||||
}
|
||||
|
||||
// Do further validation only if we're actually planning to invite to a workspace
|
||||
// (maybe the invitation is implicitly there, but user already is a member of the workspace)
|
||||
const isInvitingToWorkspace =
|
||||
primaryWorkspaceResourceTarget || (workspace && !workspace.role)
|
||||
primaryWorkspaceResourceTarget || (workspace && !workspaceRole)
|
||||
if (!isInvitingToWorkspace) {
|
||||
return [...baseTargets]
|
||||
}
|
||||
@@ -236,7 +251,7 @@ export const collectAndValidateWorkspaceTargetsFactory =
|
||||
}
|
||||
|
||||
// Only check this on creation, on finalization its fine if the user's already a member
|
||||
if (workspace.role && !finalizingInvite) {
|
||||
if (workspaceRole && !finalizingInvite) {
|
||||
throw new InviteCreateValidationError(
|
||||
'The target user is already a member of the specified workspace'
|
||||
)
|
||||
|
||||
@@ -6,16 +6,18 @@ import {
|
||||
GetWorkspaceSeatTypeToProjectRoleMapping,
|
||||
IntersectProjectCollaboratorsAndWorkspaceCollaborators,
|
||||
QueryAllWorkspaceProjects,
|
||||
UpdateWorkspaceRole
|
||||
UpdateWorkspaceRole,
|
||||
ValidateWorkspaceMemberProjectRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import {
|
||||
WorkspaceInvalidProjectError,
|
||||
WorkspaceInvalidRoleError,
|
||||
WorkspaceNotFoundError,
|
||||
WorkspaceQueryError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import { GetProject, UpdateProject } from '@/modules/core/domain/projects/operations'
|
||||
import { chunk } from 'lodash'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { Roles, StreamRoles } from '@speckle/shared'
|
||||
import {
|
||||
GetStreamCollaborators,
|
||||
LegacyGetStreams,
|
||||
@@ -42,6 +44,7 @@ import {
|
||||
upsertWorkspaceFactory
|
||||
} from '@/modules/workspaces/repositories/workspaces'
|
||||
import {
|
||||
GetWorkspaceRoleAndSeat,
|
||||
GetWorkspaceRolesAndSeats,
|
||||
GetWorkspaceWithPlan,
|
||||
WorkspaceSeatType
|
||||
@@ -296,6 +299,69 @@ export const getWorkspaceSeatTypeToProjectRoleMappingFactory =
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the specified workspace member can have the specified project role
|
||||
*/
|
||||
export const validateWorkspaceMemberProjectRoleFactory =
|
||||
(deps: {
|
||||
getWorkspaceRoleAndSeat: GetWorkspaceRoleAndSeat
|
||||
getWorkspaceWithPlan: GetWorkspaceWithPlan
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: GetWorkspaceRoleToDefaultProjectRoleMapping
|
||||
getWorkspaceSeatTypeToProjectRoleMapping: GetWorkspaceSeatTypeToProjectRoleMapping
|
||||
}): ValidateWorkspaceMemberProjectRole =>
|
||||
async (params) => {
|
||||
const { workspaceId, userId, projectRole } = params
|
||||
|
||||
const roleSeatParams = {
|
||||
workspaceId,
|
||||
userId
|
||||
}
|
||||
|
||||
const [currentWorkspaceRoleAndSeat, workspace] = await Promise.all([
|
||||
deps.getWorkspaceRoleAndSeat(roleSeatParams),
|
||||
deps.getWorkspaceWithPlan({ workspaceId })
|
||||
])
|
||||
|
||||
if (!workspace || !currentWorkspaceRoleAndSeat?.role) return
|
||||
const {
|
||||
role: { role: workspaceRole },
|
||||
seat
|
||||
} = currentWorkspaceRoleAndSeat
|
||||
const seatType = seat?.type || WorkspaceSeatType.Viewer
|
||||
|
||||
let allowedRoles: StreamRoles[]
|
||||
const isNewPlan = workspace.plan && isNewPlanType(workspace.plan.name)
|
||||
if (isNewPlan) {
|
||||
const workspaceAllowedRoles = (
|
||||
await deps.getWorkspaceRoleToDefaultProjectRoleMapping({
|
||||
workspaceId
|
||||
})
|
||||
).allowed[workspaceRole]
|
||||
const seatAllowedRoles = (
|
||||
await deps.getWorkspaceSeatTypeToProjectRoleMapping({
|
||||
workspaceId
|
||||
})
|
||||
).allowed[seatType]
|
||||
allowedRoles = Array.from(
|
||||
new Set(workspaceAllowedRoles).intersection(new Set(seatAllowedRoles))
|
||||
)
|
||||
} else {
|
||||
const roleMapping = await deps.getWorkspaceRoleToDefaultProjectRoleMapping({
|
||||
workspaceId
|
||||
})
|
||||
allowedRoles = roleMapping.allowed[workspaceRole]
|
||||
}
|
||||
|
||||
if (!allowedRoles.includes(projectRole)) {
|
||||
// User's workspace role does not allow the requested project role
|
||||
throw new WorkspaceInvalidRoleError(
|
||||
isNewPlan
|
||||
? `User's workspace seat type '${seatType}' does not allow project role '${projectRole}'.`
|
||||
: `User's workspace role '${workspaceRole}' does not allow project role '${projectRole}'.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const createWorkspaceProjectFactory =
|
||||
(deps: { getDefaultRegion: GetDefaultRegion }) =>
|
||||
async (params: { input: WorkspaceProjectCreateInput; ownerId: string }) => {
|
||||
|
||||
Reference in New Issue
Block a user