diff --git a/models/card/src/index.ts b/models/card/src/index.ts index 289a65710d..7cf3c700c4 100644 --- a/models/card/src/index.ts +++ b/models/card/src/index.ts @@ -392,7 +392,6 @@ export function createModel (builder: Builder): void { { label: card.string.CardApplication, icon: card.icon.Card, - accessLevel: AccountRole.User, alias: cardId, hidden: false, locationResolver: card.resolver.Location, @@ -401,7 +400,6 @@ export function createModel (builder: Builder): void { specials: [ { id: 'browser', - accessLevel: AccountRole.User, label: core.string.Spaces, icon: view.icon.List, component: workbench.component.SpecialView, diff --git a/models/love/src/index.ts b/models/love/src/index.ts index 2326f2a4a7..98fa64b375 100644 --- a/models/love/src/index.ts +++ b/models/love/src/index.ts @@ -285,7 +285,6 @@ export function createModel (builder: Builder): void { workbench.class.Application, core.space.Model, { - accessLevel: AccountRole.DocGuest, label: love.string.Office, icon: love.icon.Love, alias: loveId, diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index fbe68c57fb..dbde07d201 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import type { Account, AccountUuid, Doc, Domain, PersonId, Ref } from './classes' +import type { Account, AccountRole, AccountUuid, Doc, Domain, PersonId, Ref } from './classes' import { type MeasureContext } from './measurements' import { type DocumentQuery, type FindOptions } from './storage' import type { DocumentUpdate, Tx } from './tx' @@ -38,7 +38,16 @@ export interface StorageIterator { close: (ctx: MeasureContext) => Promise } -export type BroadcastTargets = Record AccountUuid[] | undefined> +export interface BroadcastTargetResult { + target: AccountUuid[] +} + +export interface BroadcastExcludeResult { + exclude: AccountUuid[] +} + +export type BroadcastResult = BroadcastTargetResult | BroadcastExcludeResult | undefined +export type BroadcastTargets = Record Promise> export interface SessionData { broadcast: { @@ -55,7 +64,13 @@ export interface SessionData { admin?: boolean isTriggerCtx?: boolean workspace: WorkspaceIds - socialStringsToUsers: Map + socialStringsToUsers: Map< + PersonId, + { + accontUuid: AccountUuid + role: AccountRole + } + > asyncRequests?: ((ctx: MeasureContext) => Promise)[] } diff --git a/server-plugins/activity-resources/src/references.ts b/server-plugins/activity-resources/src/references.ts index 627fca396a..90b48a541f 100644 --- a/server-plugins/activity-resources/src/references.ts +++ b/server-plugins/activity-resources/src/references.ts @@ -105,7 +105,7 @@ export async function getPersonNotificationTxes ( const docSpace = (await control.findAll(control.ctx, core.class.Space, { _id: space }, { limit: 1 }))[0] if (docSpace === undefined) return res - const senderAccount = control.ctx.contextData.socialStringsToUsers.get(senderId) + const senderAccount = control.ctx.contextData.socialStringsToUsers.get(senderId)?.accontUuid let collaborators: AccountUuid[] = [] diff --git a/server-plugins/contact/src/utils.ts b/server-plugins/contact/src/utils.ts index 4927edff6d..e82c37baeb 100644 --- a/server-plugins/contact/src/utils.ts +++ b/server-plugins/contact/src/utils.ts @@ -84,7 +84,7 @@ export async function getPerson (control: TriggerControl, personId: PersonId): P const { contextData } = control.ctx const account: AccountUuid | undefined = contextData.account.socialIds.includes(personId) ? contextData.account.uuid - : contextData.socialStringsToUsers.get(personId) + : contextData.socialStringsToUsers.get(personId)?.accontUuid if (account !== undefined) { return (await control.findAll(control.ctx, contact.class.Person, { personUuid: account }))[0] @@ -178,7 +178,7 @@ export async function getSocialIdsByAccounts ( } export async function getAccountBySocialId (control: TriggerControl, socialId: PersonId): Promise { - const contextAccount = control.ctx.contextData.socialStringsToUsers.get(socialId) + const contextAccount = control.ctx.contextData.socialStringsToUsers.get(socialId)?.accontUuid if (contextAccount != null) { return contextAccount } diff --git a/server-plugins/notification-resources/src/utils.ts b/server-plugins/notification-resources/src/utils.ts index fcdbdb69e1..8f2c4cf84e 100644 --- a/server-plugins/notification-resources/src/utils.ts +++ b/server-plugins/notification-resources/src/utils.ts @@ -534,7 +534,7 @@ export async function getSenderInfo ( control: TriggerControl ): Promise { const controlAccount = control.ctx.contextData.account - let account: AccountUuid | undefined = control.ctx.contextData.socialStringsToUsers.get(socialId) + let account: AccountUuid | undefined = control.ctx.contextData.socialStringsToUsers.get(socialId)?.accontUuid if (account == null && controlAccount.socialIds.includes(socialId)) { account = controlAccount.uuid diff --git a/server-plugins/time-resources/src/index.ts b/server-plugins/time-resources/src/index.ts index 8821ba4502..fbedf41dd8 100644 --- a/server-plugins/time-resources/src/index.ts +++ b/server-plugins/time-resources/src/index.ts @@ -329,9 +329,9 @@ export async function OnToDoCreate (txes: TxCUD[], control: TriggerControl) await control.apply(control.ctx, txes) const ids = txes.map((it) => it._id) - control.ctx.contextData.broadcast.targets.notifications = (it) => { + control.ctx.contextData.broadcast.targets.notifications = async (it) => { if (ids.includes(it._id)) { - return [receiverInfo.account] + return { target: [receiverInfo.account] } } } } diff --git a/server-plugins/tracker-resources/src/index.ts b/server-plugins/tracker-resources/src/index.ts index a15c204b57..ccea211887 100644 --- a/server-plugins/tracker-resources/src/index.ts +++ b/server-plugins/tracker-resources/src/index.ts @@ -167,8 +167,10 @@ export async function OnProjectRemove (txes: Tx[], control: TriggerControl): Pro } } } - control.ctx.contextData.broadcast.targets.projectRemove = (it) => { - return [] + control.ctx.contextData.broadcast.targets.projectRemove = async (it) => { + return { + target: [] + } } return result } diff --git a/server/core/src/types.ts b/server/core/src/types.ts index 96a110adcf..9b39066857 100644 --- a/server/core/src/types.ts +++ b/server/core/src/types.ts @@ -15,6 +15,7 @@ import { type ServerApi as CommunicationApi } from '@hcengineering/communication-sdk-types' import { + type AccountRole, type Account, type AccountUuid, type Branding, @@ -550,7 +551,13 @@ export interface ClientSessionCtx { ctx: MeasureContext pipeline: Pipeline - socialStringsToUsers: Map + socialStringsToUsers: Map< + PersonId, + { + accontUuid: AccountUuid + role: AccountRole + } + > requestId: ReqId | undefined sendResponse: (id: ReqId | undefined, msg: any) => Promise sendPong: () => void diff --git a/server/core/src/utils.ts b/server/core/src/utils.ts index 9abfe9466f..9d02bcc1e7 100644 --- a/server/core/src/utils.ts +++ b/server/core/src/utils.ts @@ -1,4 +1,5 @@ import core, { + type AccountRole, ClientConnectEvent, WorkspaceEvent, generateId, @@ -172,7 +173,13 @@ export class SessionDataImpl implements SessionData { _removedMap: Map, Doc> | undefined, _contextCache: Map | undefined, readonly modelDb: ModelDb, - readonly socialStringsToUsers: Map, + readonly socialStringsToUsers: Map< + PersonId, + { + accontUuid: AccountUuid + role: AccountRole + } + >, readonly service: string ) { this._removedMap = _removedMap diff --git a/server/middleware/src/broadcast.ts b/server/middleware/src/broadcast.ts index 75b55c6920..cc3fe24f06 100644 --- a/server/middleware/src/broadcast.ts +++ b/server/middleware/src/broadcast.ts @@ -14,6 +14,8 @@ // import { + type BroadcastExcludeResult, + type BroadcastResult, TxProcessor, type AccountUuid, type BroadcastTargets, @@ -76,6 +78,7 @@ export class BroadcastMiddleware extends BaseMiddleware implements Middleware { // Combine targets by sender const toSendTarget = new Map() + const excluded = new Map() const getTxes = (key: AccountUuid | ''): Tx[] => { let txes = toSendTarget.get(key) @@ -88,9 +91,9 @@ export class BroadcastMiddleware extends BaseMiddleware implements Middleware { // Put current user as send target for (const txd of tx) { - let target: AccountUuid[] | undefined + let target: BroadcastResult for (const tt of Object.values(targets ?? {})) { - target = tt(txd) + target = await tt(txd) if (target !== undefined) { break } @@ -103,8 +106,20 @@ export class BroadcastMiddleware extends BaseMiddleware implements Middleware { v.push(txd) } } else { - for (const t of target) { - getTxes(t).push(txd) + if (isExlcude(target)) { + for (const e of target.exclude) { + const txes = excluded.get(e) + if (txes === undefined) { + excluded.set(e, [txd]) + } else { + txes.push(txd) + } + } + continue + } else { + for (const t of target.target) { + getTxes(t).push(txd) + } } } } @@ -134,6 +149,11 @@ export class BroadcastMiddleware extends BaseMiddleware implements Middleware { for (const [k, v] of toSendTarget.entries()) { void handleSend(ctx, v, k as AccountUuid) } + + for (const [k, v] of excluded.entries()) { + void handleSend(ctx, v, undefined, [k]) + } + // Send all other except us. await handleSend(ctx, toSendAll, undefined, Array.from(toSendTarget.keys()) as AccountUuid[]) } @@ -158,3 +178,7 @@ export class BroadcastMiddleware extends BaseMiddleware implements Middleware { this.broadcast.broadcast(ctx, [bevent], target, exclude) } } + +function isExlcude (result: BroadcastResult): result is BroadcastExcludeResult { + return (result as BroadcastExcludeResult).exclude !== undefined +} diff --git a/server/middleware/src/identity.ts b/server/middleware/src/identity.ts index e94cde9bc8..308819ed90 100644 --- a/server/middleware/src/identity.ts +++ b/server/middleware/src/identity.ts @@ -54,7 +54,7 @@ export class IdentityMiddleware extends BaseMiddleware implements Middleware { return this.provideTx(ctx, txes) } function checkTx (tx: Tx): void { - const mxAccount = ctx.contextData.socialStringsToUsers.get(tx.modifiedBy) + const mxAccount = ctx.contextData.socialStringsToUsers.get(tx.modifiedBy)?.accontUuid if (mxAccount === undefined || mxAccount !== account.uuid) { throw new PlatformError( new Status(Severity.ERROR, platform.status.AccountMismatch, { diff --git a/server/middleware/src/private.ts b/server/middleware/src/private.ts index 685020bce7..07cd9210ac 100644 --- a/server/middleware/src/private.ts +++ b/server/middleware/src/private.ts @@ -70,20 +70,21 @@ export class PrivateMiddleware extends BaseMiddleware implements Middleware { tx (ctx: MeasureContext, txes: Tx[]): Promise { for (const tx of txes) { - let target: AccountUuid[] | undefined if (this.isTargetDomain(tx)) { const account = ctx.contextData.account if (!account.socialIds.includes(tx.modifiedBy) && account.uuid !== systemAccountUuid) { throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) } - const modifiedByAccount = ctx.contextData.socialStringsToUsers.get(tx.modifiedBy) - target = [account.uuid, systemAccountUuid] + const modifiedByAccount = ctx.contextData.socialStringsToUsers.get(tx.modifiedBy)?.accontUuid + const target = [account.uuid, systemAccountUuid] if (modifiedByAccount !== undefined && !target.includes(modifiedByAccount)) { target.push(modifiedByAccount) } - ctx.contextData.broadcast.targets['checkDomain' + account.uuid] = (tx) => { + ctx.contextData.broadcast.targets['checkDomain' + account.uuid] = async (tx) => { if (this.isTargetDomain(tx)) { - return target + return { + target + } } } } diff --git a/server/middleware/src/spaceSecurity.ts b/server/middleware/src/spaceSecurity.ts index f5598ffef2..0bd45fa2e5 100644 --- a/server/middleware/src/spaceSecurity.ts +++ b/server/middleware/src/spaceSecurity.ts @@ -46,7 +46,8 @@ import core, { shouldShowArchived, systemAccountUuid, toFindResult, - type SessionData + type SessionData, + type Collaborator } from '@hcengineering/core' import platform, { PlatformError, Severity, Status } from '@hcengineering/platform' import { @@ -266,10 +267,12 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar params: null } ctx.contextData.broadcast.txes.push(tx) - ctx.contextData.broadcast.targets['security' + tx._id] = (it) => { + ctx.contextData.broadcast.targets['security' + tx._id] = async (it) => { // TODO: I'm not sure it is called if (it._id === tx._id) { - return targets + return { + target: targets + } } } } @@ -282,7 +285,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar private broadcastAll (ctx: MeasureContext, space: SpaceWithMembers): void { const { socialStringsToUsers } = ctx.contextData - const accounts = Array.from(new Set(socialStringsToUsers.values())) + const accounts = Array.from(new Set(Array.from(socialStringsToUsers.values()).map((v) => v.accontUuid))) this.brodcastEvent(ctx, accounts, space._id) } @@ -447,12 +450,35 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar } } - ctx.contextData.broadcast.targets.spaceSec = (tx) => { + ctx.contextData.broadcast.targets.spaceSec = async (tx) => { + if (this.systemSpaces.has(tx.objectSpace) || this.mainSpaces.has(tx.objectSpace)) { + const cud = tx as TxCUD + if (cud.objectClass === undefined) return undefined + const collabSec = this.context.modelDb.findAllSync(core.class.ClassCollaborators, { + attachedTo: cud.objectClass + })[0] + if (collabSec?.provideSecurity === true) { + const guests = new Set() + for (const val of ctx.contextData.socialStringsToUsers.values()) { + if ([AccountRole.Guest, AccountRole.ReadOnlyGuest].includes(val.role)) { + guests.add(val.accontUuid) + } + } + const collabs = (await this.next?.findAll(ctx, core.class.Collaborator, { + attachedTo: cud.objectId + })) as Collaborator[] + for (const collab of collabs) { + guests.delete(collab.collaborator) + } + return { exclude: Array.from(guests) } + } + return undefined + } + const space = this.spacesMap.get(tx.objectSpace) if (space === undefined) return undefined - if (this.systemSpaces.has(space._id) || this.mainSpaces.has(space._id)) return undefined - return space.members.length === 0 ? undefined : this.getTargets(space?.members) + return space.members.length === 0 ? undefined : { target: this.getTargets(space?.members) } } await this.next?.handleBroadcast(ctx) diff --git a/server/server/src/sessionManager.ts b/server/server/src/sessionManager.ts index 8eccae8f51..2e102cebb3 100644 --- a/server/server/src/sessionManager.ts +++ b/server/server/src/sessionManager.ts @@ -1083,12 +1083,27 @@ export class TSessionManager implements SessionManager { } // TODO: cache this map and update when sessions created/closed - getActiveSocialStringsToUsersMap (workspace: WorkspaceUuid, ...extra: Session[]): Map { + getActiveSocialStringsToUsersMap ( + workspace: WorkspaceUuid, + ...extra: Session[] + ): Map< + PersonId, + { + accontUuid: AccountUuid + role: AccountRole + } + > { const ws = this.workspaces.get(workspace) if (ws === undefined) { return new Map() } - const res = new Map() + const res = new Map< + PersonId, + { + accontUuid: AccountUuid + role: AccountRole + } + >() for (const s of [...Array.from(ws.sessions.values()).map((it) => it.session), ...extra]) { const sessionAccount = s.getUser() if (sessionAccount === systemAccountUuid) { @@ -1096,7 +1111,10 @@ export class TSessionManager implements SessionManager { } const userSocialIds = s.getUserSocialIds() for (const id of userSocialIds) { - res.set(id, sessionAccount) + res.set(id, { + accontUuid: sessionAccount, + role: s.getRawAccount().role + }) } } return res diff --git a/services/github/server-github-resources/src/index.ts b/services/github/server-github-resources/src/index.ts index 8e787a862e..caf5c28dba 100644 --- a/services/github/server-github-resources/src/index.ts +++ b/services/github/server-github-resources/src/index.ts @@ -29,10 +29,10 @@ import tracker from '@hcengineering/tracker' */ export async function OnGithubBroadcast (txes: Tx[], control: TriggerControl): Promise { // Enhance broadcast to send DocSyncInfo change only to system account. - control.ctx.contextData.broadcast.targets.github = (it) => { + control.ctx.contextData.broadcast.targets.github = async (it) => { if (TxProcessor.isExtendsCUD(it._class)) { if ((it as TxCUD).objectClass === github.class.DocSyncInfo) { - return [systemAccountUuid] + return { target: [systemAccountUuid] } } } }