EQMS-1302: fixed RBAC bypass for space / team related wizards and popups (develop port)

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>
This commit is contained in:
Victor Ilyushchenko 2025-05-16 22:39:03 +03:00
parent ede948676f
commit be8503ff9a
No known key found for this signature in database
GPG Key ID: CEC5BF115E7283DF
6 changed files with 52 additions and 33 deletions

View File

@ -32,6 +32,7 @@ import {
type PermissionsStore,
type Person,
type PersonsByPermission,
type MembersBySpace,
type SocialIdentity
} from '@hcengineering/contact'
import core, {
@ -732,19 +733,33 @@ export async function resolveLocationData (loc: Location): Promise<LocationData>
}
export function checkMyPermission (_id: Ref<Permission>, space: Ref<TypedSpace>, store: PermissionsStore): boolean {
const arePermissionsDisabled = getMetadata(core.metadata.DisablePermissions) ?? false
if (arePermissionsDisabled) return true
return (store.whitelist.has(space) || store.ps[space]?.has(_id)) ?? false
}
export function getPermittedPersons (
_id: Ref<Permission>,
space: Ref<TypedSpace>,
store: PermissionsStore
): Array<Ref<Person>> {
const arePermissionsDisabled = getMetadata(core.metadata.DisablePermissions) ?? false
if (arePermissionsDisabled) return Array.from(store.ms[space] ?? [])
return store.whitelist.has(space) ? Array.from(store.ms[space] ?? []) : Array.from(store.ap[space]?.[_id] ?? [])
}
const spacesStore = writable<Space[]>([])
export const permissionsStore = derived([spacesStore, personRefByAccountUuidStore], ([spaces, personRefByAccount]) => {
const whitelistedSpaces = new Set<Ref<Space>>()
const permissionsBySpace: PermissionsBySpace = {}
const employeesByPermission: PersonsByPermission = {}
const membersBySpace: MembersBySpace = {}
const client = getClient()
const hierarchy = client.getHierarchy()
for (const s of spaces) {
membersBySpace[s._id] = new Set(s.members.map((m) => personRefByAccount.get(m)).filter(notEmpty))
if (hierarchy.isDerived(s._class, core.class.TypedSpace)) {
const type = client.getModel().findAllSync(core.class.SpaceType, { _id: (s as TypedSpace).type })[0]
const mixin = type?.targetClass
@ -790,6 +805,7 @@ export const permissionsStore = derived([spacesStore, personRefByAccountUuidStor
return {
ps: permissionsBySpace,
ap: employeesByPermission,
ms: membersBySpace,
whitelist: whitelistedSpaces
}
})
@ -815,6 +831,7 @@ spaceTypesQuery.query(core.class.SpaceType, {}, (types) => {
projection: {
_id: 1,
type: 1,
members: 1,
...targetClasses
} as any
}

View File

@ -52,9 +52,11 @@ export const AVATAR_COLORS: ColorDefinition[] = [
export type PermissionsBySpace = Record<Ref<Space>, Set<Ref<Permission>>>
export type PersonsByPermission = Record<Ref<Space>, Record<Ref<Permission>, Set<Ref<Person>>>>
export type MembersBySpace = Record<Ref<Space>, Set<Ref<Person>>>
export interface PermissionsStore {
ps: PermissionsBySpace
ap: PersonsByPermission
ms: MembersBySpace
whitelist: Set<Ref<Space>>
}

View File

@ -4,18 +4,18 @@
//
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { RequestStatus } from '@hcengineering/request'
import { Label, ModernDialog, showPopup } from '@hcengineering/ui'
import { getClient } from '@hcengineering/presentation'
import { Employee } from '@hcengineering/contact'
import { Class, Ref } from '@hcengineering/core'
import { UserBoxItems, permissionsStore } from '@hcengineering/contact-resources'
import { UserBoxItems, getPermittedPersons, permissionsStore } from '@hcengineering/contact-resources'
import documents, {
ControlledDocument,
ControlledDocumentState,
DocumentRequest
} from '@hcengineering/controlled-documents'
import { Class, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { RequestStatus } from '@hcengineering/request'
import { Label, ModernDialog, showPopup } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import documentsRes from '../plugin'
import { sendApprovalRequest, sendReviewRequest } from '../utils'
@ -38,7 +38,7 @@
const permissionId = isReviewRequest ? documents.permission.ReviewDocument : documents.permission.ApproveDocument
$: permissionsSpace =
controlledDoc.space === documents.space.UnsortedTemplates ? documents.space.QualityDocuments : controlledDoc.space
$: permittedPeople = $permissionsStore.ap[permissionsSpace]?.[permissionId] ?? new Set()
$: permittedPeople = new Set(getPermittedPersons(permissionId, permissionsSpace, $permissionsStore))
$: permittedEmployees = Array.from(permittedPeople) as Ref<Employee>[]
let docRequest: DocumentRequest | undefined

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { type Doc, type Ref, type Space } from '@hcengineering/core'
import { TypedSpace, type Doc, type Ref, type Space } from '@hcengineering/core'
import documents, {
type DocumentSpace,
type DocumentSpaceType,
@ -23,7 +23,7 @@
} from '@hcengineering/controlled-documents'
import { SpaceSelector, getClient } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import { permissionsStore } from '@hcengineering/contact-resources'
import { checkMyPermission, permissionsStore } from '@hcengineering/contact-resources'
import { $locationStep as locationStep, locationStepUpdated } from '../../../stores/wizards/create-document'
import DocumentParentSelector from '../../hierarchy/DocumentParentSelector.svelte'
@ -82,9 +82,9 @@
$: canProceed = $locationStep.space !== undefined && $locationStep.project !== undefined
$: hasParentSelector = $locationStep.space !== documents.space.UnsortedTemplates
$: restrictedSpaces = Object.entries($permissionsStore.ps)
.filter(([, pss]) => !pss.has(documents.permission.CreateDocument))
.map(([s]) => s) as Ref<Space>[]
$: restrictedSpaces = Object.keys($permissionsStore.ps).filter(
(s) => !checkMyPermission(documents.permission.CreateDocument, s as Ref<TypedSpace>, $permissionsStore)
) as Ref<TypedSpace>[]
$: spaceQuery = isTemplate
? { _id: { $nin: restrictedSpaces }, archived: false, _class: { $nin: externalSpaces } }

View File

@ -13,12 +13,12 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Label } from '@hcengineering/ui'
import { TypedSpace, type Data, type Ref, type Permission } from '@hcengineering/core'
import { type Employee, type PermissionsStore, getCurrentEmployee } from '@hcengineering/contact'
import { getCurrentEmployee, type Employee } from '@hcengineering/contact'
import { UserBoxItems, getPermittedPersons, permissionsStore } from '@hcengineering/contact-resources'
import documents, { type ControlledDocument } from '@hcengineering/controlled-documents'
import { UserBoxItems, permissionsStore } from '@hcengineering/contact-resources'
import { TypedSpace, type Data, type Ref } from '@hcengineering/core'
import { Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let controlledDoc: Data<ControlledDocument>
export let space: Ref<TypedSpace>
@ -34,23 +34,23 @@
$: permissionsSpace = space === documents.space.UnsortedTemplates ? documents.space.QualityDocuments : space
function getPermittedPersons (
permission: Ref<Permission>,
space: Ref<TypedSpace>,
permissionsStore: PermissionsStore
): Ref<Employee>[] {
return Array.from(permissionsStore.ap[space]?.[permission] ?? []) as Ref<Employee>[]
}
$: permittedReviewers = getPermittedPersons(
documents.permission.ReviewDocument,
permissionsSpace,
$permissionsStore
) as Ref<Employee>[]
$: permittedReviewers = getPermittedPersons(documents.permission.ReviewDocument, permissionsSpace, $permissionsStore)
$: permittedApprovers = getPermittedPersons(documents.permission.ApproveDocument, permissionsSpace, $permissionsStore)
$: permittedApprovers = getPermittedPersons(
documents.permission.ApproveDocument,
permissionsSpace,
$permissionsStore
) as Ref<Employee>[]
$: permittedCoAuthors = getPermittedPersons(
documents.permission.CoAuthorDocument,
permissionsSpace,
$permissionsStore
).filter((person) => person !== currentEmployee)
).filter((person) => person !== currentEmployee) as Ref<Employee>[]
function handleUsersUpdated (type: 'reviewers' | 'approvers' | 'coAuthors', users: Ref<Employee>[]): void {
dispatch('update', { type, users })

View File

@ -25,10 +25,10 @@
type Project,
type ProjectDocument
} from '@hcengineering/controlled-documents'
import { type Doc, type Ref, type Space } from '@hcengineering/core'
import { TypedSpace, type Doc, type Ref, type Space } from '@hcengineering/core'
import presentation, { getClient, SpaceSelector } from '@hcengineering/presentation'
import { Button, Label } from '@hcengineering/ui'
import { permissionsStore } from '@hcengineering/contact-resources'
import { checkMyPermission, permissionsStore } from '@hcengineering/contact-resources'
import { createEventDispatcher } from 'svelte'
import documentsRes from '../../../plugin'
@ -139,9 +139,9 @@
const externalSpaces = hierarchy.getDescendants(documents.class.ExternalSpace)
$: hasParentSelector = targetSpaceId !== documents.space.UnsortedTemplates
$: permissionRestrictedSpaces = Object.entries($permissionsStore.ps)
.filter(([, pss]) => !pss.has(documents.permission.CreateDocument))
.map(([s]) => s) as Ref<Space>[]
$: permissionRestrictedSpaces = Object.keys($permissionsStore.ps).filter(
(s) => !checkMyPermission(documents.permission.CreateDocument, s as Ref<TypedSpace>, $permissionsStore)
) as Ref<TypedSpace>[]
$: restrictedSpaces =
sourceSpaceId !== undefined ? permissionRestrictedSpaces.concat(sourceSpaceId) : permissionRestrictedSpaces