Add type/tag permissions (#10384)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2026-01-10 16:32:26 +05:00
committed by GitHub
parent 58f78b6839
commit 42f2141d0a
6 changed files with 366 additions and 67 deletions
+1 -1
View File
@@ -1 +1 @@
"0.7.340"
"0.7.343"
+160
View File
@@ -17,6 +17,8 @@ import cardPlugin, { cardId, DOMAIN_CARD, type Card, type Role } from '@hcengine
import core, {
DOMAIN_MODEL,
TxOperations,
type Class,
type ClassPermission,
type Client,
type Data,
type Doc,
@@ -121,11 +123,169 @@ export const cardOperation: MigrateOperation = {
state: 'version-for-versionable-types',
mode: 'upgrade',
func: addVersionForVersionableTypes
},
{
state: 'migrate-restricted-permissions',
mode: 'upgrade',
func: migrateRestrictedPermissions
}
])
}
}
async function migrateRestrictedPermissions (_client: MigrationUpgradeClient): Promise<void> {
const client = new TxOperations(_client, core.account.System)
const hierarchy = client.getHierarchy()
const desc = hierarchy.getDescendants(card.class.Card)
const permissions = await client.findAll(core.class.ClassPermission, { objectClass: { $in: desc } })
const restrictedTargets = new Set<Ref<Class<Doc>>>()
for (const perm of permissions) {
if (perm.targetClass !== undefined) {
restrictedTargets.add(perm.targetClass)
}
}
const targets = await client.findAll(card.class.MasterTag, { _id: { $in: [...restrictedTargets] } })
for (const masterTag of targets) {
const isMixin = hierarchy.isMixin(masterTag._id)
const objectClass = hierarchy.getBaseClass(masterTag._id)
if (isMixin) {
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxMixin,
txMatch: {
mixin: masterTag._id
},
scope: 'space',
forbid: false,
label: card.string.AddTagPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxMixin,
txMatch: {
mixin: masterTag._id
},
scope: 'space',
forbid: true,
label: card.string.ForbidAddTagPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_forbidden` as Ref<ClassPermission>
)
const key = `operations.$unset.${masterTag._id}`
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxUpdateDoc,
txMatch: {
[key]: {
$exists: true
}
},
scope: 'space',
forbid: false,
label: card.string.RemoveTag,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxUpdateDoc,
txMatch: {
[key]: { $exists: true }
},
scope: 'space',
forbid: true,
label: card.string.ForbidRemoveTag,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_forbidden` as Ref<ClassPermission>
)
} else {
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxCreateDoc,
scope: 'space',
forbid: false,
label: card.string.CreateCardPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxCreateDoc,
scope: 'space',
forbid: true,
label: card.string.ForbidCreateCardPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_forbidden` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxRemoveDoc,
scope: 'space',
forbid: false,
label: card.string.RemoveCard,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxRemoveDoc,
txMatch: {
objectClass: masterTag._id
},
scope: 'space',
forbid: true,
label: card.string.ForbidRemoveCard,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_forbidden` as Ref<ClassPermission>
)
}
}
}
async function addVersionForVersionableTypes (client: MigrationUpgradeClient): Promise<void> {
const txOp = new TxOperations(client, core.account.System)
const versionableTypes = await client.findAll(card.class.MasterTag, {})
+1 -23
View File
@@ -17,7 +17,7 @@ import { type Card, cardId } from '@hcengineering/card'
import card from '@hcengineering/card-resources/src/plugin'
import type { Client, Doc, Ref } from '@hcengineering/core'
import {} from '@hcengineering/core'
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
import { mergeIds, type Resource } from '@hcengineering/platform'
import { type TagCategory } from '@hcengineering/tags'
import { type Location, type ResolvedLocation } from '@hcengineering/ui/src/types'
import { type Action, type ActionCategory, type ViewAction } from '@hcengineering/view'
@@ -39,28 +39,6 @@ export default mergeIds(cardId, card, {
PublicLink: '' as Ref<Action<Doc, any>>,
Duplicate: '' as Ref<Action>
},
string: {
CreateCardPersmissionDescription: '' as IntlString,
UpdateCardPersmissionDescription: '' as IntlString,
RemoveCardPersmissionDescription: '' as IntlString,
AddTagPersmissionDescription: '' as IntlString,
RemoveTagPersmissionDescription: '' as IntlString,
RemoveCard: '' as IntlString,
UpdateCard: '' as IntlString,
CreateCardPermission: '' as IntlString,
AddTagPermission: '' as IntlString,
RemoveTag: '' as IntlString,
ForbidCreateCardPersmissionDescription: '' as IntlString,
ForbidUpdateCardPersmissionDescription: '' as IntlString,
ForbidRemoveCardPersmissionDescription: '' as IntlString,
ForbidAddTagPersmissionDescription: '' as IntlString,
ForbidRemoveTagPersmissionDescription: '' as IntlString,
ForbidRemoveCard: '' as IntlString,
ForbidUpdateCard: '' as IntlString,
ForbidCreateCardPermission: '' as IntlString,
ForbidAddTagPermission: '' as IntlString,
ForbidRemoveTag: '' as IntlString
},
category: {
Card: '' as Ref<ActionCategory>,
Labels: '' as Ref<TagCategory>
@@ -14,18 +14,17 @@
-->
<script lang="ts">
import { MasterTag } from '@hcengineering/card'
import { ClassPermission, Ref } from '@hcengineering/core'
import { getClient, MessageBox } from '@hcengineering/presentation'
import { ClassAttributes } from '@hcengineering/setting-resources'
import setting from '@hcengineering/setting-resources/src/plugin'
import { ButtonIcon, showPopup } from '@hcengineering/ui'
import card from '../../plugin'
import { getClient, MessageBox } from '@hcengineering/presentation'
import core, { ClassPermission, Ref } from '@hcengineering/core'
import view from '@hcengineering/view'
import { createTypePermissions } from '../../utils'
export let masterTag: MasterTag
const client = getClient()
const hierarchy = client.getHierarchy()
let isRestricted: boolean =
client.getModel().findObject(getPermissionRef(false)) !== undefined ||
@@ -43,43 +42,7 @@
message: setting.string.RestrictedAttributeWarning,
action: async () => {
isRestricted = true
const isMixin = hierarchy.isMixin(masterTag._id)
const objectClass = hierarchy.getBaseClass(masterTag._id)
const txClass = isMixin ? core.class.TxMixin : core.class.TxUpdateDoc
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass,
txMatch: {
[isMixin ? 'mixin' : 'objectClass']: masterTag._id
},
scope: 'space',
forbid: false,
label: view.string.AllowAttributeChanges,
description: masterTag.label,
targetClass: masterTag._id
},
getPermissionRef(false)
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass,
txMatch: {
[isMixin ? 'mixin' : 'objectClass']: masterTag._id
},
scope: 'space',
forbid: true,
label: view.string.ForbidAttributeChanges,
description: masterTag.label,
targetClass: masterTag._id
},
getPermissionRef(true)
)
await createTypePermissions(masterTag)
}
},
'top',
+21 -1
View File
@@ -135,6 +135,26 @@ export default mergeIds(cardId, card, {
EnableVersioning: '' as IntlString,
EnableVersioningConfirm: '' as IntlString,
NewVersionConfirmation: '' as IntlString,
RelationCopyDescr: '' as IntlString
RelationCopyDescr: '' as IntlString,
CreateCardPersmissionDescription: '' as IntlString,
UpdateCardPersmissionDescription: '' as IntlString,
RemoveCardPersmissionDescription: '' as IntlString,
AddTagPersmissionDescription: '' as IntlString,
RemoveTagPersmissionDescription: '' as IntlString,
RemoveCard: '' as IntlString,
UpdateCard: '' as IntlString,
CreateCardPermission: '' as IntlString,
AddTagPermission: '' as IntlString,
RemoveTag: '' as IntlString,
ForbidCreateCardPersmissionDescription: '' as IntlString,
ForbidUpdateCardPersmissionDescription: '' as IntlString,
ForbidRemoveCardPersmissionDescription: '' as IntlString,
ForbidAddTagPersmissionDescription: '' as IntlString,
ForbidRemoveTagPersmissionDescription: '' as IntlString,
ForbidRemoveCard: '' as IntlString,
ForbidUpdateCard: '' as IntlString,
ForbidCreateCardPermission: '' as IntlString,
ForbidAddTagPermission: '' as IntlString,
ForbidRemoveTag: '' as IntlString
}
})
+179 -1
View File
@@ -13,10 +13,11 @@
import { type AccountClient, getClient as getAccountClientRaw } from '@hcengineering/account-client'
import { Analytics } from '@hcengineering/analytics'
import { type Card, CardEvents, cardId, type CardSpace, type MasterTag } from '@hcengineering/card'
import { type Card, CardEvents, cardId, type CardSpace, type MasterTag, type Tag } from '@hcengineering/card'
import core, {
AccountRole,
type Class,
type ClassPermission,
type Client,
type Data,
type Doc,
@@ -97,6 +98,183 @@ export async function deleteMasterTag (tag: MasterTag | undefined, onDelete?: ()
}
}
export async function createTypePermissions (masterTag: MasterTag | Tag): Promise<void> {
const client = getClient()
const hierarchy = client.getHierarchy()
const isMixin = hierarchy.isMixin(masterTag._id)
const objectClass = hierarchy.getBaseClass(masterTag._id)
const txClass = isMixin ? core.class.TxMixin : core.class.TxUpdateDoc
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass,
txMatch: {
[isMixin ? 'mixin' : 'objectClass']: masterTag._id
},
scope: 'space',
forbid: false,
label: view.string.AllowAttributeChanges,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass,
txMatch: {
[isMixin ? 'mixin' : 'objectClass']: masterTag._id
},
scope: 'space',
forbid: true,
label: view.string.ForbidAttributeChanges,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_forbidden` as Ref<ClassPermission>
)
if (isMixin) {
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxMixin,
txMatch: {
mixin: masterTag._id
},
scope: 'space',
forbid: false,
label: card.string.AddTagPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxMixin,
txMatch: {
mixin: masterTag._id
},
scope: 'space',
forbid: true,
label: card.string.ForbidAddTagPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_forbidden` as Ref<ClassPermission>
)
const key = `operations.$unset.${masterTag._id}`
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxUpdateDoc,
txMatch: {
[key]: {
$exists: true
}
},
scope: 'space',
forbid: false,
label: card.string.RemoveTag,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxUpdateDoc,
txMatch: {
[key]: { $exists: true }
},
scope: 'space',
forbid: true,
label: card.string.ForbidRemoveTag,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_forbidden` as Ref<ClassPermission>
)
} else {
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxCreateDoc,
scope: 'space',
forbid: false,
label: card.string.CreateCardPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxCreateDoc,
scope: 'space',
forbid: true,
label: card.string.ForbidCreateCardPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_forbidden` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxRemoveDoc,
scope: 'space',
forbid: false,
label: card.string.RemoveCard,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxRemoveDoc,
txMatch: {
objectClass: masterTag._id
},
scope: 'space',
forbid: true,
label: card.string.ForbidRemoveCard,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_forbidden` as Ref<ClassPermission>
)
}
}
async function cloneCard (
origin: Card,
overrideProps: Record<string, any>,