Disable owner space security (#2705)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-03-02 17:45:55 +06:00 committed by GitHub
parent 7b14204aab
commit f0bcbd1c82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 49 deletions

View File

@ -13,10 +13,10 @@
// limitations under the License. // limitations under the License.
// //
import { AccountRole } from '@hcengineering/core'
import { Builder } from '@hcengineering/model' import { Builder } from '@hcengineering/model'
import core from './component' import core from './component'
import { import {
TFullTextSearchContext,
TArrOf, TArrOf,
TAttachedDoc, TAttachedDoc,
TAttribute, TAttribute,
@ -30,6 +30,8 @@ import {
TEnum, TEnum,
TEnumOf, TEnumOf,
TFulltextData, TFulltextData,
TFullTextSearchContext,
TIndexStageState,
TInterface, TInterface,
TMixin, TMixin,
TObj, TObj,
@ -46,8 +48,7 @@ import {
TTypeRelatedDocument, TTypeRelatedDocument,
TTypeString, TTypeString,
TTypeTimestamp, TTypeTimestamp,
TVersion, TVersion
TIndexStageState
} from './core' } from './core'
import { TAccount, TSpace } from './security' import { TAccount, TSpace } from './security'
import { TUserStatus } from './transient' import { TUserStatus } from './transient'
@ -105,4 +106,14 @@ export function createModel (builder: Builder): void {
TConfiguration, TConfiguration,
TConfigurationElement TConfigurationElement
) )
builder.createDoc(
core.class.Account,
core.space.Model,
{
email: 'anticrm@hc.engineering',
role: AccountRole.Owner
},
core.account.System
)
} }

View File

@ -13,28 +13,9 @@
// limitations under the License. // limitations under the License.
// //
import core, { AccountRole, Client, TxOperations } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
export const coreOperation: MigrateOperation = { export const coreOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {}, async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> { async upgrade (client: MigrationUpgradeClient): Promise<void> {}
await createSystemAccount(client)
}
}
async function createSystemAccount (client: Client): Promise<void> {
const current = await client.findOne(core.class.Account, { _id: core.account.System })
if (current === undefined) {
const txop = new TxOperations(client, core.account.System)
await txop.createDoc(
core.class.Account,
core.space.Model,
{
email: 'anticrm@hc.engineering',
role: AccountRole.Owner
},
core.account.System
)
}
} }

View File

@ -53,7 +53,7 @@ export class PrivateMiddleware extends BaseMiddleware implements Middleware {
const txCUD = tx as TxCUD<Doc> const txCUD = tx as TxCUD<Doc>
const domain = this.storage.hierarchy.getDomain(txCUD.objectClass) const domain = this.storage.hierarchy.getDomain(txCUD.objectClass)
if (this.targetDomains.includes(domain)) { if (this.targetDomains.includes(domain)) {
const account = await getUser(this.storage, ctx) const account = (await getUser(this.storage, ctx))._id
if (account !== tx.modifiedBy && account !== core.account.System) { if (account !== tx.modifiedBy && account !== core.account.System) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
} }
@ -74,7 +74,7 @@ export class PrivateMiddleware extends BaseMiddleware implements Middleware {
const domain = this.storage.hierarchy.getDomain(_class) const domain = this.storage.hierarchy.getDomain(_class)
if (this.targetDomains.includes(domain)) { if (this.targetDomains.includes(domain)) {
const account = await getUser(this.storage, ctx) const account = await getUser(this.storage, ctx)
if (account !== core.account.System) { if (account._id !== core.account.System) {
newQuery = { newQuery = {
...query, ...query,
modifiedBy: account modifiedBy: account
@ -95,7 +95,7 @@ export class PrivateMiddleware extends BaseMiddleware implements Middleware {
async isAvailable (ctx: SessionContext, doc: Doc): Promise<boolean> { async isAvailable (ctx: SessionContext, doc: Doc): Promise<boolean> {
const domain = this.storage.hierarchy.getDomain(doc._class) const domain = this.storage.hierarchy.getDomain(doc._class)
if (!this.targetDomains.includes(domain)) return true if (!this.targetDomains.includes(domain)) return true
const account = await getUser(this.storage, ctx) const account = (await getUser(this.storage, ctx))._id
return doc.modifiedBy === account || account === core.account.System return doc.modifiedBy === account || account === core.account.System
} }

View File

@ -39,7 +39,7 @@ import core, {
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform' import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
import { Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core' import { Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
import { BaseMiddleware } from './base' import { BaseMiddleware } from './base'
import { getUser, mergeTargets } from './utils' import { getUser, isOwner, mergeTargets } from './utils'
/** /**
* @public * @public
@ -241,8 +241,8 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
const space = this.privateSpaces[tx.objectSpace] const space = this.privateSpaces[tx.objectSpace]
if (space !== undefined) { if (space !== undefined) {
const account = await getUser(this.storage, ctx) const account = await getUser(this.storage, ctx)
if (account !== core.account.System) { if (!isOwner(account)) {
const allowed = this.allowedSpaces[account] const allowed = this.allowedSpaces[account._id]
if (allowed === undefined || !allowed.includes(isSpace ? (cudTx.objectId as Ref<Space>) : tx.objectSpace)) { if (allowed === undefined || !allowed.includes(isSpace ? (cudTx.objectId as Ref<Space>) : tx.objectSpace)) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
} }
@ -255,22 +255,21 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
return [res[0], res[1], mergeTargets(targets, res[2])] return [res[0], res[1], mergeTargets(targets, res[2])]
} }
private async getAllAllowedSpaces (ctx: SessionContext): Promise<Ref<Space>[]> { private async getAllAllowedSpaces (account: Account): Promise<Ref<Space>[]> {
let userSpaces: Ref<Space>[] = [] let userSpaces: Ref<Space>[] = []
try { try {
const account = await getUser(this.storage, ctx) userSpaces = this.allowedSpaces[account._id] ?? []
userSpaces = this.allowedSpaces[account] ?? [] return [...userSpaces, account._id as string as Ref<Space>, ...this.publicSpaces, ...this.systemSpaces]
return [...userSpaces, account as string as Ref<Space>, ...this.publicSpaces, ...this.systemSpaces]
} catch { } catch {
return [...this.publicSpaces, ...this.systemSpaces] return [...this.publicSpaces, ...this.systemSpaces]
} }
} }
private async mergeQuery<T extends Doc>( private async mergeQuery<T extends Doc>(
ctx: SessionContext, account: Account,
query: ObjQueryType<T['space']> query: ObjQueryType<T['space']>
): Promise<ObjQueryType<T['space']>> { ): Promise<ObjQueryType<T['space']>> {
const spaces = await this.getAllAllowedSpaces(ctx) const spaces = await this.getAllAllowedSpaces(account)
if (typeof query === 'string') { if (typeof query === 'string') {
if (!spaces.includes(query)) { if (!spaces.includes(query)) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
@ -290,17 +289,22 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
options?: FindOptions<T> options?: FindOptions<T>
): Promise<FindResult<T>> { ): Promise<FindResult<T>> {
const newQuery = query const newQuery = query
if (query.space !== undefined) { const account = await getUser(this.storage, ctx)
newQuery.space = await this.mergeQuery(ctx, query.space) if (!isOwner(account)) {
} else { if (query.space !== undefined) {
const spaces = await this.getAllAllowedSpaces(ctx) newQuery.space = await this.mergeQuery(account, query.space)
newQuery.space = { $in: spaces } } else {
const spaces = await this.getAllAllowedSpaces(account)
newQuery.space = { $in: spaces }
}
} }
const findResult = await this.provideFindAll(ctx, _class, newQuery, options) const findResult = await this.provideFindAll(ctx, _class, newQuery, options)
if (options?.lookup !== undefined) { if (!isOwner(account)) {
for (const object of findResult) { if (options?.lookup !== undefined) {
if (object.$lookup !== undefined) { for (const object of findResult) {
await this.filterLookup(ctx, object.$lookup) if (object.$lookup !== undefined) {
await this.filterLookup(ctx, object.$lookup)
}
} }
} }
} }
@ -310,8 +314,8 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
async isUnavailable (ctx: SessionContext, space: Ref<Space>): Promise<boolean> { async isUnavailable (ctx: SessionContext, space: Ref<Space>): Promise<boolean> {
if (this.privateSpaces[space] === undefined) return false if (this.privateSpaces[space] === undefined) return false
const account = await getUser(this.storage, ctx) const account = await getUser(this.storage, ctx)
if (account === core.account.System) return false if (isOwner(account)) return false
return !this.allowedSpaces[account]?.includes(space) return !this.allowedSpaces[account._id]?.includes(space)
} }
async filterLookup<T extends Doc>(ctx: SessionContext, lookup: LookupData<T>): Promise<void> { async filterLookup<T extends Doc>(ctx: SessionContext, lookup: LookupData<T>): Promise<void> {

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
// //
import core, { Account, Ref, ServerStorage } from '@hcengineering/core' import core, { Account, AccountRole, ServerStorage } from '@hcengineering/core'
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform' import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
import { SessionContext } from '@hcengineering/server-core' import { SessionContext } from '@hcengineering/server-core'
@ -29,13 +29,28 @@ export function mergeTargets (current: string[] | undefined, prev: string[] | un
return res return res
} }
export async function getUser (storage: ServerStorage, ctx: SessionContext): Promise<Ref<Account>> { export async function getUser (storage: ServerStorage, ctx: SessionContext): Promise<Account> {
if (ctx.userEmail === undefined) { if (ctx.userEmail === undefined) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
} }
const account = (await storage.modelDb.findAll(core.class.Account, { email: ctx.userEmail }))[0] const account = (await storage.modelDb.findAll(core.class.Account, { email: ctx.userEmail }))[0]
if (account === undefined) { if (account === undefined) {
if (ctx.userEmail === 'anticrm@hc.engineering') {
return {
_id: core.account.System,
_class: core.class.Account,
role: AccountRole.Owner,
email: 'anticrm@hc.engineering',
space: core.space.Model,
modifiedBy: core.account.System,
modifiedOn: 0
}
}
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
} }
return account._id return account
}
export function isOwner (account: Account): boolean {
return account.role === AccountRole.Owner || account._id === core.account.System
} }