mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-30 21:45:01 +02:00
Enforce contact edit boundaries for regular users on both the UI and transaction middleware so users can maintain their own profile details without being able to change other employees' person records or communication channels. Changes: - Reject regular-user CUD transactions that update another employee Person or channels attached to that employee, including nested TxApplyIf payloads. - Allow regular users to edit their own contact details and non-employee contacts, while Maintainers and Owners continue to manage employee records. - Share contact editability logic in contact resources and use it in person editing, channel editors, and channel presenters so the UI hides or disables blocked actions. - Add middleware tests covering own-person edits, blocked cross-employee person/channel edits, and maintainer bypass behavior. Validation: - git diff --check - rushx svelte-check in plugins/contact-resources - targeted Rush tests for @hcengineering/middleware and @hcengineering/contact-resources - targeted Rush build for @hcengineering/middleware and @hcengineering/contact-resources Behavioral effect: Regular users can update their own profile contact details, but attempts to edit another employee's person record or attached email, phone, or GitHub channels are blocked even if sent directly to the server.
127 lines
3.6 KiB
Svelte
127 lines
3.6 KiB
Svelte
<!--
|
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
|
// Copyright © 2021, 2022 Hardcore Engineering Inc.
|
|
//
|
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License. You may
|
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
//
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
-->
|
|
<script lang="ts">
|
|
import type { AttachedData, Class, Doc, Ref } from '@hcengineering/core'
|
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
|
import { ButtonKind, ButtonSize, closeTooltip, showPopup } from '@hcengineering/ui'
|
|
|
|
import { Channel, ChannelProvider, type Person } from '@hcengineering/contact'
|
|
import { restrictionStore } from '@hcengineering/view-resources'
|
|
import contact from '../plugin'
|
|
import { canEditPersonContactDetails } from '../utils'
|
|
import ChannelsDropdown from './ChannelsDropdown.svelte'
|
|
|
|
export let attachedTo: Ref<Doc>
|
|
export let attachedClass: Ref<Class<Doc>>
|
|
export let integrations: Set<Ref<Doc>> | undefined = undefined
|
|
export let editable: boolean = true
|
|
export let allowOpen: boolean = !$restrictionStore.disableNavigation
|
|
export let focusIndex = -1
|
|
|
|
export let kind: ButtonKind = 'link-bordered'
|
|
export let size: ButtonSize = 'small'
|
|
export let length: 'short' | 'full' = 'full'
|
|
export let shape: 'circle' | undefined = 'circle'
|
|
export let restricted: Ref<ChannelProvider>[] = []
|
|
|
|
let channels: Channel[] = []
|
|
let attachedPerson: Person | undefined = undefined
|
|
|
|
const query = createQuery()
|
|
$: attachedTo &&
|
|
query.query(
|
|
contact.class.Channel,
|
|
{
|
|
attachedTo
|
|
},
|
|
(res) => {
|
|
channels = res
|
|
}
|
|
)
|
|
|
|
const personQuery = createQuery()
|
|
$: if (attachedClass === contact.class.Person) {
|
|
personQuery.query(
|
|
contact.class.Person,
|
|
{
|
|
_id: attachedTo as Ref<Person>
|
|
},
|
|
(res) => {
|
|
attachedPerson = res[0]
|
|
}
|
|
)
|
|
} else {
|
|
personQuery.unsubscribe()
|
|
attachedPerson = undefined
|
|
}
|
|
|
|
$: effectiveEditable =
|
|
attachedClass === contact.class.Person
|
|
? editable && attachedPerson !== undefined && canEditPersonContactDetails(attachedPerson)
|
|
: editable
|
|
|
|
const client = getClient()
|
|
|
|
async function remove (value: Channel | AttachedData<Channel>): Promise<void> {
|
|
if (!effectiveEditable) return
|
|
if ('_id' in value) {
|
|
await client.remove(value)
|
|
}
|
|
}
|
|
|
|
async function saveHandler (value: Channel | AttachedData<Channel>): Promise<void> {
|
|
if (!effectiveEditable) return
|
|
if ('_id' in value) {
|
|
await client.update(value, {
|
|
value: value.value
|
|
})
|
|
} else {
|
|
await client.addCollection(contact.class.Channel, contact.space.Contacts, attachedTo, attachedClass, 'channels', {
|
|
value: value.value,
|
|
provider: value.provider
|
|
})
|
|
}
|
|
}
|
|
|
|
function _open (ev: any) {
|
|
if (ev.detail.presenter !== undefined) {
|
|
if (allowOpen) {
|
|
closeTooltip()
|
|
showPopup(ev.detail.presenter, { channel: ev.detail.channel }, 'float')
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<ChannelsDropdown
|
|
value={channels}
|
|
{kind}
|
|
{size}
|
|
{length}
|
|
{integrations}
|
|
editable={effectiveEditable}
|
|
{restricted}
|
|
{shape}
|
|
{focusIndex}
|
|
on:remove={(e) => {
|
|
remove(e.detail)
|
|
}}
|
|
on:save={(e) => {
|
|
saveHandler(e.detail)
|
|
}}
|
|
on:open={_open}
|
|
/>
|