From c1adb7c94bef23ff98716eefcc550336484eb4d0 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Sat, 13 Jan 2024 20:09:06 +0600 Subject: [PATCH] UBERF-4915 (#4351) Signed-off-by: Denis Bykhov --- server/middleware/src/spaceSecurity.ts | 83 +++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/server/middleware/src/spaceSecurity.ts b/server/middleware/src/spaceSecurity.ts index d93d91bf08..b0e30744f4 100644 --- a/server/middleware/src/spaceSecurity.ts +++ b/server/middleware/src/spaceSecurity.ts @@ -53,6 +53,7 @@ import { getUser, isOwner, isSystem, mergeTargets } from './utils' export class SpaceSecurityMiddleware extends BaseMiddleware implements Middleware { private allowedSpaces: Record, Ref[]> = {} private privateSpaces: Record, Space | undefined> = {} + private domainSpaces: Record>> = {} private publicSpaces: Ref[] = [] private readonly systemSpaces = [ core.space.Configuration, @@ -100,6 +101,40 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar this.addSpace(space) } this.publicSpaces = (await this.storage.findAll(ctx, core.class.Space, { private: false })).map((p) => p._id) + await this.initDomains(ctx) + } + + private async initDomains (ctx: MeasureContext): Promise { + const classesPerDomain: Record>[]> = {} + const skip = new Set>>() + const classes = this.storage.hierarchy.getDescendants(core.class.Doc) + for (const _class of classes) { + if (skip.has(_class)) continue + try { + const parent = this.storage.hierarchy.getParentClass(_class) + skip.add(parent) + const domain = this.storage.hierarchy.findDomain(parent) + if (domain === undefined) continue + classesPerDomain[domain] = classesPerDomain[domain] ?? [] + classesPerDomain[domain].push(parent) + this.storage.hierarchy.getDescendants(parent).forEach((p) => skip.add(p)) + } catch {} + } + for (const domain in classesPerDomain) { + for (const _class of classesPerDomain[domain]) { + const field = this.getKey(_class) + const spaces = await this.storage.findAll( + ctx, + _class, + {}, + { + projection: { [field]: 1 } + } + ) + this.domainSpaces[domain] = this.domainSpaces[domain] ?? new Set() + spaces.forEach((p) => this.domainSpaces[domain].add((p as any)[field] as Ref)) + } + } } private removeMemberSpace (member: Ref, space: Ref): void { @@ -308,6 +343,29 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar return targets } + private processTxSpaceDomain (tx: TxCUD): void { + const actualTx = TxProcessor.extractTx(tx) + if (actualTx._class === core.class.TxCreateDoc) { + const ctx = actualTx as TxCreateDoc + const doc = TxProcessor.createDoc2Doc(ctx) + const key = this.getKey(ctx.objectClass) + const space = (doc as any)[key] + if (space === undefined) return + const domain = this.storage.hierarchy.getDomain(ctx.objectClass) + this.domainSpaces[domain] = this.domainSpaces[domain] ?? new Set() + this.domainSpaces[domain].add(space) + } else if (actualTx._class === core.class.TxUpdateDoc) { + const updTx = actualTx as TxUpdateDoc + const key = this.getKey(updTx.objectClass) + const space = (updTx.operations as any)[key] + if (space !== undefined) { + const domain = this.storage.hierarchy.getDomain(updTx.objectClass) + this.domainSpaces[domain] = this.domainSpaces[domain] ?? new Set() + this.domainSpaces[domain].add(space) + } + } + } + private async proccessTx (ctx: SessionContext, tx: Tx): Promise { const h = this.storage.hierarchy if (h.isDerived(tx._class, core.class.TxCUD)) { @@ -316,6 +374,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar if (isSpace) { await this.handleTx(ctx, cudTx as TxCUD) } + this.processTxSpaceDomain(tx as TxCUD) if (h.isDerived(cudTx.objectClass, core.class.Account) && cudTx._class === core.class.TxUpdateDoc) { const ctx = cudTx as TxUpdateDoc if (ctx.operations.role !== undefined) { @@ -335,7 +394,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar return [res[0], res[1], mergeTargets(targets, res[2])] } - private async getAllAllowedSpaces (account: Account): Promise[]> { + private getAllAllowedSpaces (account: Account): Ref[] { let userSpaces: Ref[] = [] try { userSpaces = this.allowedSpaces[account._id] ?? [] @@ -345,11 +404,18 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar } } - private async mergeQuery( + private filterByDomain (domain: string, spaces: Ref[]): Ref[] { + const domainSpaces = this.domainSpaces[domain] + if (domainSpaces === undefined) return [] + return spaces.filter((p) => domainSpaces.has(p)) + } + + private mergeQuery( account: Account, - query: ObjQueryType - ): Promise> { - const spaces = await this.getAllAllowedSpaces(account) + query: ObjQueryType, + domain: string + ): ObjQueryType { + const spaces = this.filterByDomain(domain, this.getAllAllowedSpaces(account)) if (query == null) { return { $in: spaces } } @@ -379,6 +445,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar query: DocumentQuery, options?: FindOptions ): Promise> { + const domain = this.storage.hierarchy.getDomain(_class) const newQuery = query const account = await getUser(this.storage, ctx) const field = this.getKey(_class) @@ -386,9 +453,9 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar if (!isSystem(account)) { if (!isOwner(account) || !this.storage.hierarchy.isDerived(_class, core.class.Space)) { if (query[field] !== undefined) { - ;(newQuery as any)[field] = await this.mergeQuery(account, query[field]) + ;(newQuery as any)[field] = this.mergeQuery(account, query[field], domain) } else { - const spaces = await this.getAllAllowedSpaces(account) + const spaces = this.filterByDomain(domain, this.getAllAllowedSpaces(account)) ;(newQuery as any)[field] = { $in: spaces } } } @@ -414,7 +481,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar const newQuery = { ...query } const account = await getUser(this.storage, ctx) if (!isSystem(account)) { - newQuery.spaces = await this.getAllAllowedSpaces(account) + newQuery.spaces = this.getAllAllowedSpaces(account) } const result = await this.provideSearchFulltext(ctx, newQuery, options) return result