Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions foundations/core/packages/core/src/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,7 @@ export interface ClassCollaborators<T extends Doc> extends Doc {
allFields?: boolean // for all (PersonId | Ref<Employee> | PersonId[] | Ref<Employee>[]) attributes
fields: (keyof T)[] // PersonId | Ref<Employee> | PersonId[] | Ref<Employee>[]
provideSecurity?: boolean // If true, will provide security for collaborators
provideAttachedSecurity?: boolean // If true, will provide security for collaborators of attached doc
}

export interface Collaborator extends AttachedDoc {
Expand Down
28 changes: 22 additions & 6 deletions foundations/server/packages/middleware/src/spaceSecurity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,22 +484,38 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
const space = this.spacesMap.get(tx.objectSpace)
if (space === undefined) return undefined

// For all other spaces broadcast to space members + guests that are collaborators for objects with collab security enabled
let collabTargets: AccountUuid[] = []
const collabSec = getClassCollaborators(this.context.modelDb, this.context.hierarchy, cud.objectClass)
if (collabSec?.provideSecurity === true) {
const getCollabTargets = async (_id: Ref<Doc>): Promise<AccountUuid[]> => {
const guests = new Set<AccountUuid>()
for (const val of ctx.contextData.socialStringsToUsers.values()) {
if ([AccountRole.Guest, AccountRole.ReadOnlyGuest].includes(val.role)) {
guests.add(val.accontUuid)
}
}
const collaboratorObjs = (await this.next?.findAll(ctx, core.class.Collaborator, {
attachedTo: cud.objectId
attachedTo: _id
})) as Collaborator[]

collabTargets = collaboratorObjs.map((it) => it.collaborator).filter((it) => guests.has(it))
return collaboratorObjs.map((it) => it.collaborator).filter((it) => guests.has(it))
}

// For all other spaces broadcast to space members
// + guests that are collaborators for objects with collab security enabled
// + guests that are collaborators for attached objects with collab security enabled
let collabTargets: AccountUuid[] = []
const collabSec = getClassCollaborators(this.context.modelDb, this.context.hierarchy, cud.objectClass)
if (collabSec?.provideSecurity === true) {
collabTargets = await getCollabTargets(cud.objectId)
} else if (cud.attachedTo != null && cud.attachedToClass != null) {
const attachedCollabSec = getClassCollaborators(
this.context.modelDb,
this.context.hierarchy,
cud.attachedToClass
)
if (attachedCollabSec?.provideSecurity === true) {
collabTargets = await getCollabTargets(cud.attachedTo)
}
}

const spaceTargets = space.members.length === 0 ? [] : this.getTargets(space?.members)
const target = [...collabTargets, ...spaceTargets]

Expand Down
13 changes: 9 additions & 4 deletions foundations/server/packages/postgres/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,11 +638,16 @@ abstract class PostgresAdapterBase implements DbAdapter {
const res = `EXISTS (SELECT 1 FROM ${translateDomain(DOMAIN_SPACE)} sec WHERE sec._id = ${domain}.${key} AND sec."workspaceId" = ${vars.add(this.workspaceId, '::uuid')} AND ${q})`

const collabSec = getClassCollaborators(this.modelDb, this.hierarchy, _class)
if (collabSec?.provideSecurity === true && [AccountRole.Guest, AccountRole.ReadOnlyGuest].includes(acc.role)) {
const collab = `OR EXISTS (SELECT 1 FROM ${translateDomain(DOMAIN_COLLABORATOR)} collab_sec WHERE collab_sec."workspaceId" = ${vars.add(this.workspaceId, '::uuid')} AND collab_sec."attachedTo" = ${domain}._id AND collab_sec.collaborator = '${acc.uuid}')`
return `AND (${res} ${collab})`
let collabRes = ''
if ([AccountRole.Guest, AccountRole.ReadOnlyGuest].includes(acc.role)) {
if (collabSec?.provideSecurity === true) {
collabRes += ` OR EXISTS (SELECT 1 FROM ${translateDomain(DOMAIN_COLLABORATOR)} collab_sec WHERE collab_sec."workspaceId" = ${vars.add(this.workspaceId, '::uuid')} AND collab_sec."attachedTo" = ${domain}._id AND collab_sec.collaborator = '${acc.uuid}')`
}
if (collabSec?.provideAttachedSecurity === true) {
collabRes += ` OR EXISTS (SELECT 1 FROM ${translateDomain(DOMAIN_COLLABORATOR)} collab_sec WHERE collab_sec."workspaceId" = ${vars.add(this.workspaceId, '::uuid')} AND collab_sec."attachedTo" = ${domain}."attachedTo" AND collab_sec.collaborator = '${acc.uuid}')`
}
}
return `AND (${res})`
return `AND (${res}${collabRes})`
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions models/activity/src/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ export function buildNotifications (builder: Builder): void {

builder.createDoc<ClassCollaborators<ActivityMessage>>(core.class.ClassCollaborators, core.space.Model, {
attachedTo: activity.class.ActivityMessage,
fields: ['createdBy', 'repliedPersons']
fields: ['createdBy', 'repliedPersons'],
provideAttachedSecurity: true
})

builder.createDoc<ClassCollaborators<DocUpdateMessage>>(core.class.ClassCollaborators, core.space.Model, {
attachedTo: activity.class.DocUpdateMessage,
fields: ['createdBy', 'repliedPersons']
fields: ['createdBy', 'repliedPersons'],
provideAttachedSecurity: true
})

builder.mixin(activity.class.ActivityMessage, core.class.Class, notification.mixin.NotificationContextPresenter, {
Expand Down
1 change: 1 addition & 0 deletions models/core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ export class TClassCollaborators extends TDoc implements ClassCollaborators<Doc>
allFields?: boolean
fields!: (keyof Doc)[]
provideSecurity?: boolean
provideAttachedSecurity?: boolean
}

@Model(core.class.Collaborator, core.class.Doc, DOMAIN_COLLABORATOR)
Expand Down
Loading