feat(authz): Project.canCreateModel and Project.canMoveToWorkspace policies (#4342)
* feat(authz): Project.canCreateModel policy * feat(authz): Project.canMoveToWorkspace policy * fix(authz): expose policies as permissions objects * chore(authz): actually use the policies lol * chore(authz): add tests for new policies * fix(authz): skip affected test * fix(authz): pr comments * fix(authz): better errors, better tests * chore(authz): remove references to deleted error
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
import { db } from '@/db/knex'
|
||||
import { getPaginatedProjectModelsTotalCountFactory } from '@/modules/core/repositories/branches'
|
||||
import { legacyGetStreamsFactory } from '@/modules/core/repositories/streams'
|
||||
import { getWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing'
|
||||
import { defineModuleLoaders } from '@/modules/loaders'
|
||||
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
|
||||
import {
|
||||
getUserSsoSessionFactory,
|
||||
getWorkspaceSsoProviderRecordFactory
|
||||
} from '@/modules/workspaces/repositories/sso'
|
||||
import { getWorkspaceRoleForUserFactory } from '@/modules/workspaces/repositories/workspaces'
|
||||
import { queryAllWorkspaceProjectsFactory } from '@/modules/workspaces/services/projects'
|
||||
import { getWorkspaceModelCountFactory } from '@/modules/workspaces/services/workspaceLimits'
|
||||
import { WorkspacePaidPlanConfigs, WorkspaceUnpaidPlanConfigs } from '@speckle/shared'
|
||||
|
||||
// TODO: Move everything to use dataLoaders
|
||||
@@ -46,6 +51,21 @@ export default defineModuleLoaders(async () => {
|
||||
)?.type || null
|
||||
)
|
||||
},
|
||||
getWorkspaceModelCount: async ({ workspaceId }) => {
|
||||
// TODO: Dataloader that has to dynamically pick regional dbs?
|
||||
return await getWorkspaceModelCountFactory({
|
||||
queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({
|
||||
getStreams: legacyGetStreamsFactory({ db })
|
||||
}),
|
||||
getPaginatedProjectModelsTotalCount: async (projectId, params) => {
|
||||
const regionDb = await getProjectDbClient({ projectId })
|
||||
return await getPaginatedProjectModelsTotalCountFactory({ db: regionDb })(
|
||||
projectId,
|
||||
params
|
||||
)
|
||||
}
|
||||
})({ workspaceId })
|
||||
},
|
||||
getWorkspaceProjectCount: async ({ workspaceId }, { dataLoaders }) => {
|
||||
return await dataLoaders.workspaces!.getProjectCount.load(workspaceId)
|
||||
},
|
||||
|
||||
@@ -241,6 +241,10 @@ export type GetWorkspacesProjectsCounts = (params: {
|
||||
[workspaceId: string]: number
|
||||
}>
|
||||
|
||||
export type GetWorkspaceModelCount = (params: {
|
||||
workspaceId: string
|
||||
}) => Promise<number>
|
||||
|
||||
export type GetPaginatedWorkspaceProjectsArgs = {
|
||||
workspaceId: string
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,15 @@ export default {
|
||||
userId: ctx.userId
|
||||
})
|
||||
return Authz.toGraphqlResult(canCreateProject)
|
||||
},
|
||||
canMoveProjectToWorkspace: async (parent, args, ctx) => {
|
||||
const canMoveProjectToWorkspace =
|
||||
await ctx.authPolicies.project.canMoveToWorkspace({
|
||||
userId: ctx.userId,
|
||||
projectId: args.projectId,
|
||||
workspaceId: parent.workspaceId
|
||||
})
|
||||
return Authz.toGraphqlResult(canMoveProjectToWorkspace)
|
||||
}
|
||||
}
|
||||
} as Resolvers
|
||||
|
||||
@@ -193,7 +193,6 @@ import { getProjectFactory } from '@/modules/core/repositories/projects'
|
||||
import { getProjectRegionKey } from '@/modules/multiregion/utils/regionSelector'
|
||||
import { scheduleJob } from '@/modules/multiregion/services/queue'
|
||||
import { updateWorkspacePlanFactory } from '@/modules/gatekeeper/services/workspacePlans'
|
||||
import { OperationTypeNode } from 'graphql'
|
||||
import { GetWorkspaceCollaboratorsArgs } from '@/modules/workspaces/domain/operations'
|
||||
import { WorkspaceTeamMember } from '@/modules/workspaces/domain/types'
|
||||
import { UsersMeta } from '@/modules/core/dbSchema'
|
||||
@@ -209,6 +208,7 @@ import {
|
||||
getWorkspaceRolesAndSeatsFactory,
|
||||
getWorkspaceUserSeatFactory
|
||||
} from '@/modules/gatekeeper/repositories/workspaceSeat'
|
||||
import { mapAuthToServerError } from '@/modules/shared/helpers/errorHelper'
|
||||
|
||||
const eventBus = getEventBus()
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -1057,20 +1057,16 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
moveToWorkspace: async (_parent, args, context) => {
|
||||
const { projectId, workspaceId } = args
|
||||
|
||||
await authorizeResolver(
|
||||
context.userId,
|
||||
projectId,
|
||||
Roles.Stream.Owner,
|
||||
context.resourceAccessRules,
|
||||
OperationTypeNode.MUTATION
|
||||
)
|
||||
await authorizeResolver(
|
||||
context.userId,
|
||||
workspaceId,
|
||||
Roles.Workspace.Admin,
|
||||
context.resourceAccessRules,
|
||||
OperationTypeNode.MUTATION
|
||||
)
|
||||
const canMoveToWorkspace =
|
||||
await context.authPolicies.project.canMoveToWorkspace({
|
||||
userId: context.userId,
|
||||
projectId,
|
||||
workspaceId
|
||||
})
|
||||
|
||||
if (!canMoveToWorkspace.isOk) {
|
||||
throw mapAuthToServerError(canMoveToWorkspace.error)
|
||||
}
|
||||
|
||||
const moveProjectToWorkspace = commandFactory({
|
||||
db,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { GetPaginatedProjectModelsTotalCount } from '@/modules/core/domain/branches/operations'
|
||||
import {
|
||||
GetWorkspaceModelCount,
|
||||
QueryAllWorkspaceProjects
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
|
||||
// TODO: Optimize with single model count query per regional db
|
||||
export const getWorkspaceModelCountFactory =
|
||||
(deps: {
|
||||
queryAllWorkspaceProjects: QueryAllWorkspaceProjects
|
||||
getPaginatedProjectModelsTotalCount: GetPaginatedProjectModelsTotalCount
|
||||
}): GetWorkspaceModelCount =>
|
||||
async ({ workspaceId }) => {
|
||||
let modelCount = 0
|
||||
|
||||
for await (const projects of deps.queryAllWorkspaceProjects({ workspaceId })) {
|
||||
for (const project of projects) {
|
||||
modelCount =
|
||||
modelCount +
|
||||
(await deps.getPaginatedProjectModelsTotalCount(project.id, {
|
||||
filter: {
|
||||
onlyWithVersions: true
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
return modelCount
|
||||
}
|
||||
Reference in New Issue
Block a user