mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
UBERF-5812: Fix allow to delete based on all my accounts (#4823)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
5cd8dca583
commit
ae6059c040
@ -326,7 +326,7 @@ function defineApplication (
|
||||
spaceClass: tracker.class.Project,
|
||||
componentProps: {
|
||||
_class: tracker.class.Project,
|
||||
label: tracker.string.AllIssues,
|
||||
label: tracker.string.AllProjects,
|
||||
icon: tracker.icon.Issues
|
||||
}
|
||||
}
|
||||
|
@ -187,12 +187,22 @@ export async function setClient (_client: MeasureClient): Promise<void> {
|
||||
* @public
|
||||
*/
|
||||
export async function refreshClient (): Promise<void> {
|
||||
await liveQuery?.refreshConnect()
|
||||
for (const q of globalQueries) {
|
||||
q.refreshClient()
|
||||
if (!(liveQuery?.isClosed() ?? true)) {
|
||||
await liveQuery?.refreshConnect()
|
||||
for (const q of globalQueries) {
|
||||
q.refreshClient()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function purgeClient (): Promise<void> {
|
||||
await liveQuery?.close()
|
||||
await pipeline?.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -529,13 +539,9 @@ export function isCollectionAttr (hierarchy: Hierarchy, key: KeyedAttribute): bo
|
||||
* @public
|
||||
*/
|
||||
export function decodeTokenPayload (token: string): any {
|
||||
const parts = token.split('.')
|
||||
|
||||
const payload = parts[1]
|
||||
|
||||
const decodedPayload = atob(payload)
|
||||
|
||||
const parsedPayload = JSON.parse(decodedPayload)
|
||||
|
||||
return parsedPayload
|
||||
return JSON.parse(atob(token.split('.')[1]))
|
||||
}
|
||||
|
||||
export function isAdminUser (): boolean {
|
||||
return decodeTokenPayload(getMetadata(plugin.metadata.Token) ?? '').admin === 'true'
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ export class LiveQuery implements WithTx, Client {
|
||||
private readonly queries: Map<Ref<Class<Doc>>, Query[]> = new Map<Ref<Class<Doc>>, Query[]>()
|
||||
private readonly queue: Query[] = []
|
||||
private queryCounter: number = 0
|
||||
private closed: boolean = false
|
||||
|
||||
// A map of _class to documents.
|
||||
private readonly documentRefs = new Map<string, Map<Ref<Doc>, DocumentRef>>()
|
||||
@ -104,7 +105,12 @@ export class LiveQuery implements WithTx, Client {
|
||||
await this.refreshConnect()
|
||||
}
|
||||
|
||||
public isClosed (): boolean {
|
||||
return this.closed
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
this.closed = true
|
||||
await this.client.close()
|
||||
}
|
||||
|
||||
|
@ -13,16 +13,16 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Card } from '@hcengineering/presentation'
|
||||
import { AccountRole, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { AccountRole, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Card, isAdminUser } from '@hcengineering/presentation'
|
||||
import ui, { Button, Label } from '@hcengineering/ui'
|
||||
import { ObjectPresenter } from '@hcengineering/view-resources'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { personAccountByIdStore } from '../utils'
|
||||
import ui, { Button, Label } from '@hcengineering/ui'
|
||||
import PersonAccountRefPresenter from './PersonAccountRefPresenter.svelte'
|
||||
import PersonAccountPresenter from './PersonAccountPresenter.svelte'
|
||||
import { ObjectPresenter } from '@hcengineering/view-resources'
|
||||
import PersonAccountRefPresenter from './PersonAccountRefPresenter.svelte'
|
||||
|
||||
export let object: Doc | Doc[]
|
||||
export let deleteAction: () => void | Promise<void>
|
||||
@ -37,7 +37,8 @@
|
||||
$: canDelete =
|
||||
(skipCheck ||
|
||||
(creators.length === 1 && creators.includes(getCurrentAccount()._id as Ref<PersonAccount>)) ||
|
||||
getCurrentAccount().role === AccountRole.Owner) &&
|
||||
getCurrentAccount().role === AccountRole.Owner ||
|
||||
isAdminUser()) &&
|
||||
canDeleteExtra
|
||||
$: label = canDelete ? view.string.DeleteObject : view.string.DeletePopupNoPermissionTitle
|
||||
</script>
|
||||
|
@ -16,20 +16,21 @@
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import { AccountRole, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { tzDateCompare, type Department, type Request, type RequestType, type Staff } from '@hcengineering/hr'
|
||||
import { isAdminUser } from '@hcengineering/presentation'
|
||||
import {
|
||||
areDatesEqual,
|
||||
day as getDay,
|
||||
daysInMonth,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
eventToHTMLElement,
|
||||
day as getDay,
|
||||
getWeekDayName,
|
||||
isWeekend,
|
||||
Label,
|
||||
LabelAndProps,
|
||||
resizeObserver,
|
||||
Scroller,
|
||||
showPopup,
|
||||
tooltip,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
resizeObserver
|
||||
tooltip
|
||||
} from '@hcengineering/ui'
|
||||
import hr from '../../plugin'
|
||||
import { getHolidayDatesForEmployee, getRequests, isHoliday } from '../../utils'
|
||||
@ -183,7 +184,7 @@
|
||||
}
|
||||
|
||||
function isEditable (employee: Staff): boolean {
|
||||
return editableList.includes(employee._id) && (isFutureDate() || me.role === AccountRole.Owner)
|
||||
return editableList.includes(employee._id) && (isFutureDate() || me.role === AccountRole.Owner || isAdminUser())
|
||||
}
|
||||
|
||||
function getTooltip (requests: Request[], day: Date, staff: Staff): LabelAndProps | undefined {
|
||||
@ -306,7 +307,7 @@
|
||||
class="timeline-cell timeline-day-header flex-col-center justify-center"
|
||||
style={getCellStyle()}
|
||||
on:click={() => {
|
||||
if (isFutureDate() || me.role === AccountRole.Owner) {
|
||||
if (isFutureDate() || me.role === AccountRole.Owner || isAdminUser()) {
|
||||
setPublicHoliday(day)
|
||||
}
|
||||
}}
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { AccountRole, getCurrentAccount, roleOrder } from '@hcengineering/core'
|
||||
import presentation, { createQuery, decodeTokenPayload } from '@hcengineering/presentation'
|
||||
import { createQuery, isAdminUser } from '@hcengineering/presentation'
|
||||
import setting, { SettingsCategory } from '@hcengineering/setting'
|
||||
import {
|
||||
Component,
|
||||
@ -27,7 +27,6 @@
|
||||
} from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { clearSettingsStore } from '../store'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
|
||||
export let kind: 'navigation' | 'content' | undefined
|
||||
export let categoryName: string
|
||||
@ -39,7 +38,7 @@
|
||||
let categories: SettingsCategory[] = []
|
||||
const account = getCurrentAccount() as PersonAccount
|
||||
|
||||
const admin = decodeTokenPayload(getMetadata(presentation.metadata.Token) ?? '').admin === 'true'
|
||||
const admin = isAdminUser()
|
||||
|
||||
const settingsQuery = createQuery()
|
||||
settingsQuery.query(
|
||||
@ -48,7 +47,7 @@
|
||||
(res) => {
|
||||
categories = roleOrder[account.role] > roleOrder[AccountRole.User] ? res : res.filter((p) => !p.secured)
|
||||
if (!admin) {
|
||||
categories = categories.filter((p) => !p.adminOnly)
|
||||
categories = categories.filter((p) => !(p.adminOnly ?? false))
|
||||
}
|
||||
category = findCategory(categoryId)
|
||||
},
|
||||
|
@ -14,9 +14,17 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Project } from '@hcengineering/tracker'
|
||||
import { Icon, IconWithEmoji, getPlatformColorDef, getPlatformColorForTextDef, themeStore } from '@hcengineering/ui'
|
||||
import {
|
||||
Icon,
|
||||
IconWithEmoji,
|
||||
getPlatformColorDef,
|
||||
getPlatformColorForTextDef,
|
||||
themeStore,
|
||||
Label
|
||||
} from '@hcengineering/ui'
|
||||
import tracker from '../../plugin'
|
||||
import view from '@hcengineering/view'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
|
||||
export let value: Project | undefined
|
||||
export let inline: boolean = false
|
||||
@ -41,6 +49,9 @@
|
||||
</div>
|
||||
<span class="label no-underline nowrap" class:fs-bold={accent}>
|
||||
{value.name}
|
||||
{#if value.archived}
|
||||
<Label label={presentation.string.Archived} />
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -17,7 +17,12 @@
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import core, {
|
||||
AccountRole,
|
||||
type FindOptions,
|
||||
ClassifierKind,
|
||||
Hierarchy,
|
||||
TxProcessor,
|
||||
getCurrentAccount,
|
||||
getObjectValue,
|
||||
type Account,
|
||||
type AggregateValue,
|
||||
type AttachedDoc,
|
||||
type CategoryType,
|
||||
@ -27,9 +32,7 @@ import core, {
|
||||
type Doc,
|
||||
type DocumentQuery,
|
||||
type DocumentUpdate,
|
||||
getCurrentAccount,
|
||||
getObjectValue,
|
||||
Hierarchy,
|
||||
type FindOptions,
|
||||
type Lookup,
|
||||
type Mixin,
|
||||
type Obj,
|
||||
@ -38,38 +41,37 @@ import core, {
|
||||
type ReverseLookup,
|
||||
type ReverseLookups,
|
||||
type Space,
|
||||
type TxOperations,
|
||||
type TxCUD,
|
||||
type TxCollectionCUD,
|
||||
TxProcessor,
|
||||
type TxCreateDoc,
|
||||
type TxUpdateDoc,
|
||||
type TxMixin,
|
||||
ClassifierKind,
|
||||
type TxOperations,
|
||||
type TxUpdateDoc,
|
||||
type TypeAny
|
||||
} from '@hcengineering/core'
|
||||
import { type Restrictions } from '@hcengineering/guest'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { getResource, translate } from '@hcengineering/platform'
|
||||
import {
|
||||
type AttributeCategory,
|
||||
getAttributePresenterClass,
|
||||
getClient,
|
||||
hasResource,
|
||||
isAdminUser,
|
||||
type AttributeCategory,
|
||||
type KeyedAttribute
|
||||
} from '@hcengineering/presentation'
|
||||
import { type Restrictions } from '@hcengineering/guest'
|
||||
import {
|
||||
type AnyComponent,
|
||||
type AnySvelteComponent,
|
||||
ErrorPresenter,
|
||||
getCurrentResolvedLocation,
|
||||
getPanelURI,
|
||||
getPlatformColorForText,
|
||||
type Location,
|
||||
locationToUrl,
|
||||
navigate,
|
||||
resolvedLocationStore,
|
||||
themeStore
|
||||
themeStore,
|
||||
type AnyComponent,
|
||||
type AnySvelteComponent,
|
||||
type Location
|
||||
} from '@hcengineering/ui'
|
||||
import view, {
|
||||
type AttributeModel,
|
||||
@ -80,10 +82,10 @@ import view, {
|
||||
type ViewletDescriptor
|
||||
} from '@hcengineering/view'
|
||||
|
||||
import contact, { getName, type Contact, type PersonAccount } from '@hcengineering/contact'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import plugin from './plugin'
|
||||
import { noCategory } from './viewOptions'
|
||||
import contact, { type Contact, getName } from '@hcengineering/contact'
|
||||
|
||||
export { getFiltredKeys, isCollectionAttr } from '@hcengineering/presentation'
|
||||
|
||||
@ -386,7 +388,8 @@ export async function buildModel (options: BuildModelOptions): Promise<Attribute
|
||||
|
||||
export async function deleteObject (client: TxOperations, object: Doc): Promise<void> {
|
||||
const currentAcc = getCurrentAccount()
|
||||
if (currentAcc.role !== AccountRole.Owner && object.createdBy !== currentAcc._id) return
|
||||
const accounts = await getCurrentPersonAccounts()
|
||||
if (currentAcc.role !== AccountRole.Owner && !accounts.has(object.createdBy)) return
|
||||
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
|
||||
const adoc = object as AttachedDoc
|
||||
await client
|
||||
@ -401,13 +404,45 @@ export async function deleteObject (client: TxOperations, object: Doc): Promise<
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentPersonAccounts (): Promise<Set<Ref<Account> | undefined>> {
|
||||
return new Set(
|
||||
(
|
||||
await getClient().findAll(contact.class.PersonAccount, { person: (getCurrentAccount() as PersonAccount).person })
|
||||
).map((it) => it._id)
|
||||
)
|
||||
}
|
||||
|
||||
export async function deleteObjects (client: TxOperations, objects: Doc[], skipCheck: boolean = false): Promise<void> {
|
||||
let realObjects: Doc[] = []
|
||||
if (!skipCheck) {
|
||||
const currentAcc = getCurrentAccount()
|
||||
if (currentAcc.role !== AccountRole.Owner && objects.some((p) => p.createdBy !== currentAcc._id)) return
|
||||
|
||||
// We need to find all person current accounts
|
||||
const allPersonAccounts = await getCurrentPersonAccounts()
|
||||
|
||||
const byClass = new Map<Ref<Class<Doc>>, Doc[]>()
|
||||
for (const d of objects) {
|
||||
byClass.set(d._class, [...(byClass.get(d._class) ?? []), d])
|
||||
}
|
||||
const adminUser = isAdminUser()
|
||||
for (const [cl, docs] of byClass.entries()) {
|
||||
const realDocs = await client.findAll(cl, { _id: { $in: docs.map((it: Doc) => it._id) } })
|
||||
const notAllowed = realDocs.filter((p) => !allPersonAccounts.has(p.createdBy))
|
||||
|
||||
if (notAllowed.length > 0) {
|
||||
console.error('You are not allowed to delete this object', notAllowed)
|
||||
}
|
||||
if (currentAcc.role === AccountRole.Owner || adminUser) {
|
||||
realObjects.push(...realDocs)
|
||||
} else {
|
||||
realObjects.push(...realDocs.filter((p) => allPersonAccounts.has(p.createdBy)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
realObjects = objects
|
||||
}
|
||||
const ops = client.apply('delete')
|
||||
for (const object of objects) {
|
||||
for (const object of realObjects) {
|
||||
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
|
||||
const adoc = object as AttachedDoc
|
||||
await ops
|
||||
|
@ -521,7 +521,7 @@ export interface Action<T extends Doc = Doc, P = Record<string, any>> extends Do
|
||||
// For example, it could be global action and action for focus class, second one fill override first one.
|
||||
override?: Ref<Action>[]
|
||||
|
||||
// Avaible only for workspace owners
|
||||
// Available only for workspace owners
|
||||
secured?: boolean
|
||||
allowedForEditableContent?: boolean
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
import notification, { DocNotifyContext, InboxNotification, inboxId } from '@hcengineering/notification'
|
||||
import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import { IntlString, broadcastEvent, getMetadata, getResource } from '@hcengineering/platform'
|
||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ActionContext, createQuery, getClient, isAdminUser } from '@hcengineering/presentation'
|
||||
import setting from '@hcengineering/setting'
|
||||
import support, { SupportStatus, supportLink } from '@hcengineering/support'
|
||||
import {
|
||||
@ -633,7 +633,7 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else if employee?.active || account.role === AccountRole.Owner}
|
||||
{:else if employee?.active || account.role === AccountRole.Owner || isAdminUser()}
|
||||
<ActionHandler />
|
||||
<svg class="svg-mask">
|
||||
<clipPath id="notify-normal">
|
||||
|
@ -11,7 +11,7 @@ import core, {
|
||||
} from '@hcengineering/core'
|
||||
import login, { loginId } from '@hcengineering/login'
|
||||
import { addEventListener, broadcastEvent, getMetadata, getResource, setMetadata } from '@hcengineering/platform'
|
||||
import presentation, { closeClient, refreshClient, setClient } from '@hcengineering/presentation'
|
||||
import presentation, { closeClient, purgeClient, refreshClient, setClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
fetchMetadataLocalStorage,
|
||||
getCurrentLocation,
|
||||
@ -77,6 +77,8 @@ export async function connect (title: string): Promise<Client | undefined> {
|
||||
}
|
||||
|
||||
if (_token !== token && _client !== undefined) {
|
||||
// We need to flush all data from memory
|
||||
await purgeClient()
|
||||
await _client.close()
|
||||
_client = undefined
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import WorkbenchApp from './components/WorkbenchApp.svelte'
|
||||
import { doNavigate } from './utils'
|
||||
import Workbench from './components/Workbench.svelte'
|
||||
import ServerManager from './components/ServerManager.svelte'
|
||||
import { isAdminUser } from '@hcengineering/presentation'
|
||||
|
||||
async function hasArchiveSpaces (spaces: Space[]): Promise<boolean> {
|
||||
return spaces.find((sp) => sp.archived) !== undefined
|
||||
@ -51,7 +52,7 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
function: {
|
||||
HasArchiveSpaces: hasArchiveSpaces,
|
||||
IsOwner: async (docs: Space[]) => getCurrentAccount().role === AccountRole.Owner
|
||||
IsOwner: async (docs: Space[]) => getCurrentAccount().role === AccountRole.Owner || isAdminUser()
|
||||
},
|
||||
actionImpl: {
|
||||
Navigate: doNavigate
|
||||
|
@ -59,7 +59,7 @@ export class ConfigurationMiddleware extends BaseMiddleware implements Middlewar
|
||||
if (this.targetDomains.includes(domain)) {
|
||||
if (ctx.userEmail !== configurationAccountEmail) {
|
||||
const account = await this.getUser(ctx)
|
||||
if (account.role !== AccountRole.Owner) {
|
||||
if (account.role !== AccountRole.Owner && ctx.admin !== true) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
}
|
||||
@ -82,7 +82,7 @@ export class ConfigurationMiddleware extends BaseMiddleware implements Middlewar
|
||||
if (this.targetDomains.includes(domain)) {
|
||||
if (ctx.userEmail !== configurationAccountEmail) {
|
||||
const account = await this.getUser(ctx)
|
||||
if (account.role !== AccountRole.Owner && account._id !== core.account.System) {
|
||||
if (account.role !== AccountRole.Owner && account._id !== core.account.System && ctx.admin !== true) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
}
|
||||
|
@ -313,7 +313,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
|
||||
const space = this.privateSpaces[tx.objectSpace]
|
||||
if (space !== undefined) {
|
||||
targets = await this.getTargets(space.members)
|
||||
if (!isOwner(account)) {
|
||||
if (!isOwner(account, ctx)) {
|
||||
const cudTx = tx as TxCUD<Doc>
|
||||
const isSpace = h.isDerived(cudTx.objectClass, core.class.Space)
|
||||
const allowed = this.allowedSpaces[account._id]
|
||||
@ -482,7 +482,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
|
||||
const field = this.getKey(_class)
|
||||
|
||||
if (!isSystem(account) && account.role !== AccountRole.Guest) {
|
||||
if (!isOwner(account) || !this.storage.hierarchy.isDerived(_class, core.class.Space)) {
|
||||
if (!isOwner(account, ctx) || !this.storage.hierarchy.isDerived(_class, core.class.Space)) {
|
||||
if (query[field] !== undefined) {
|
||||
;(newQuery as any)[field] = await this.mergeQuery(account, query[field], domain)
|
||||
} else {
|
||||
@ -492,7 +492,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
|
||||
}
|
||||
}
|
||||
const findResult = await this.provideFindAll(ctx, _class, newQuery, options)
|
||||
if (!isOwner(account) && account.role !== AccountRole.Guest) {
|
||||
if (!isOwner(account, ctx) && account.role !== AccountRole.Guest) {
|
||||
if (options?.lookup !== undefined) {
|
||||
for (const object of findResult) {
|
||||
if (object.$lookup !== undefined) {
|
||||
@ -521,7 +521,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
|
||||
async isUnavailable (ctx: SessionContext, space: Ref<Space>): Promise<boolean> {
|
||||
if (this.privateSpaces[space] === undefined) return false
|
||||
const account = await getUser(this.storage, ctx)
|
||||
if (isOwner(account)) return false
|
||||
if (isOwner(account, ctx)) return false
|
||||
return !this.allowedSpaces[account._id]?.includes(space)
|
||||
}
|
||||
|
||||
|
@ -51,8 +51,8 @@ export async function getUser (storage: ServerStorage, ctx: SessionContext): Pro
|
||||
return account
|
||||
}
|
||||
|
||||
export function isOwner (account: Account): boolean {
|
||||
return account.role === AccountRole.Owner || account._id === core.account.System
|
||||
export function isOwner (account: Account, ctx: SessionContext): boolean {
|
||||
return account.role === AccountRole.Owner || account._id === core.account.System || ctx.admin === true
|
||||
}
|
||||
|
||||
export function isSystem (account: Account): boolean {
|
||||
|
Loading…
Reference in New Issue
Block a user