466 lines
15 KiB
TypeScript
466 lines
15 KiB
TypeScript
import type {
|
|||
|
|
DataAccessPolicy,
|
||
|
|
DataAccessRule,
|
||
|
|
DataAccessSection,
|
||
|
|
DataVisibility,
|
||
|
|
DataVisibilityScopes,
|
||
|
|
SectionDataAccess,
|
||
|
|
} from '@mento/shared';
|
||
|
|
import {
|
||
|
|
DATA_ACCESS_LEGACY_SCOPE,
|
||
|
|
DATA_ACCESS_SECTIONS,
|
||
|
|
migrateVisibilityToSectionAccess,
|
||
|
|
parseDataAccessPolicy,
|
||
|
|
sectionHasDataAccessLimits,
|
||
|
|
} from '@mento/shared';
|
||
|
|
import { canViewAllRecords } from '@mento/shared';
|
||
|
|
import type { Prisma } from '@prisma/client';
|
||
|
|
import type { Actor } from './record-scope';
|
||
|
|
|
||
|
|
function elevated(role: string | null | undefined): boolean {
|
||
|
|
return canViewAllRecords(role);
|
||
|
|
}
|
||
|
|
|
||
|
|
export type SectionAccessContext = {
|
||
|
|
section: SectionDataAccess;
|
||
|
|
source: 'role' | 'override' | 'default';
|
||
|
|
};
|
||
|
|
|
||
|
|
function ownerIdList(
|
||
|
|
rule: DataAccessRule,
|
||
|
|
ctx: { userId: string; teamPeerIds: string[] | null },
|
||
|
|
): string[] {
|
||
|
|
const ownerIds = new Set<string>();
|
||
|
|
if (!rule.owners?.length) return [];
|
||
|
|
if (rule.owners.includes('self')) ownerIds.add(ctx.userId);
|
||
|
|
if (rule.owners.includes('team')) {
|
||
|
|
const peers = ctx.teamPeerIds?.length ? ctx.teamPeerIds : [ctx.userId];
|
||
|
|
for (const id of peers) ownerIds.add(id);
|
||
|
|
}
|
||
|
|
return [...ownerIds];
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Resolve یک بخش: override کاربر بر نقش؛ وگرنه مهاجرت از visibility قدیمی. */
|
||
|
|
export function resolveSectionAccess(
|
||
|
|
sectionKey: DataAccessSection,
|
||
|
|
input: {
|
||
|
|
role: string;
|
||
|
|
rolePolicy?: unknown;
|
||
|
|
override?: unknown;
|
||
|
|
legacyVisibility?: DataVisibility | string | null;
|
||
|
|
legacyScopes?: DataVisibilityScopes | null;
|
||
|
|
},
|
||
|
|
): SectionAccessContext {
|
||
|
|
if (elevated(input.role)) {
|
||
|
|
return { section: { mode: 'all', rules: [] }, source: 'default' };
|
||
|
|
}
|
||
|
|
const override = parseDataAccessPolicy(input.override);
|
||
|
|
if (override[sectionKey]) {
|
||
|
|
return { section: override[sectionKey]!, source: 'override' };
|
||
|
|
}
|
||
|
|
const rolePolicy = parseDataAccessPolicy(input.rolePolicy);
|
||
|
|
if (rolePolicy[sectionKey]) {
|
||
|
|
return { section: rolePolicy[sectionKey]!, source: 'role' };
|
||
|
|
}
|
||
|
|
if (input.legacyVisibility != null || input.legacyScopes) {
|
||
|
|
return {
|
||
|
|
section: migrateVisibilityToSectionAccess(
|
||
|
|
input.legacyVisibility,
|
||
|
|
input.legacyScopes,
|
||
|
|
DATA_ACCESS_LEGACY_SCOPE[sectionKey],
|
||
|
|
),
|
||
|
|
source: 'default',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return { section: { mode: 'all', rules: [] }, source: 'default' };
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @deprecated */
|
||
|
|
export function resolveCustomersAccess(input: {
|
||
|
|
role: string;
|
||
|
|
rolePolicy?: unknown;
|
||
|
|
override?: unknown;
|
||
|
|
legacyVisibility?: DataVisibility | string | null;
|
||
|
|
legacyScopes?: DataVisibilityScopes | null;
|
||
|
|
}): SectionAccessContext {
|
||
|
|
return resolveSectionAccess('customers', input);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function resolveAllSectionAccess(input: {
|
||
|
|
role: string;
|
||
|
|
rolePolicy?: unknown;
|
||
|
|
override?: unknown;
|
||
|
|
legacyVisibility?: DataVisibility | string | null;
|
||
|
|
legacyScopes?: DataVisibilityScopes | null;
|
||
|
|
}): DataAccessPolicy {
|
||
|
|
const out: DataAccessPolicy = {};
|
||
|
|
for (const key of DATA_ACCESS_SECTIONS) {
|
||
|
|
out[key] = resolveSectionAccess(key, input).section;
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function buildContactRuleWhere(
|
||
|
|
rule: DataAccessRule,
|
||
|
|
ctx: { userId: string; teamPeerIds: string[] | null },
|
||
|
|
): Prisma.PersonWhereInput | null {
|
||
|
|
const and: Prisma.PersonWhereInput[] = [];
|
||
|
|
const ids = ownerIdList(rule, ctx);
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
if (!ids.length) return null;
|
||
|
|
and.push(ids.length === 1 ? { ownerId: ids[0]! } : { ownerId: { in: ids } });
|
||
|
|
}
|
||
|
|
if (rule.groupIds?.length) {
|
||
|
|
and.push({ groupLinks: { some: { groupId: { in: rule.groupIds } } } });
|
||
|
|
}
|
||
|
|
if (rule.sources?.length) {
|
||
|
|
and.push({
|
||
|
|
OR: [{ importSource: { in: rule.sources } }, { sourceRef: { in: rule.sources } }],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (rule.moods?.length) {
|
||
|
|
and.push({ mood: { in: rule.moods } });
|
||
|
|
}
|
||
|
|
if (!and.length) return null;
|
||
|
|
return and.length === 1 ? and[0]! : { AND: and };
|
||
|
|
}
|
||
|
|
|
||
|
|
function orParts<T>(parts: T[]): T | { OR: T[] } | { id: string } {
|
||
|
|
if (!parts.length) return { id: '__no_access__' } as T & { id: string };
|
||
|
|
return parts.length === 1 ? parts[0]! : { OR: parts };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function customersPersonWhere(
|
||
|
|
actor: Pick<Actor, 'userId' | 'role' | 'customersAccess' | 'teamPeerIds'>,
|
||
|
|
): Prisma.PersonWhereInput {
|
||
|
|
if (elevated(actor.role)) return {};
|
||
|
|
const section = actor.customersAccess ?? { mode: 'all' as const, rules: [] };
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return {};
|
||
|
|
const parts: Prisma.PersonWhereInput[] = [];
|
||
|
|
for (const rule of section.rules ?? []) {
|
||
|
|
const w = buildContactRuleWhere(rule, {
|
||
|
|
userId: actor.userId,
|
||
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
||
|
|
});
|
||
|
|
if (w) parts.push(w);
|
||
|
|
}
|
||
|
|
return orParts(parts);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function customersCompanyWhere(
|
||
|
|
actor: Pick<Actor, 'userId' | 'role' | 'customersAccess' | 'teamPeerIds'>,
|
||
|
|
): Prisma.CompanyWhereInput {
|
||
|
|
if (elevated(actor.role)) return {};
|
||
|
|
const section = actor.customersAccess ?? { mode: 'all' as const, rules: [] };
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return {};
|
||
|
|
const parts: Prisma.CompanyWhereInput[] = [];
|
||
|
|
for (const rule of section.rules ?? []) {
|
||
|
|
const and: Prisma.CompanyWhereInput[] = [];
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
const ids = ownerIdList(rule, {
|
||
|
|
userId: actor.userId,
|
||
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
||
|
|
});
|
||
|
|
if (!ids.length) continue;
|
||
|
|
and.push(ids.length === 1 ? { ownerId: ids[0]! } : { ownerId: { in: ids } });
|
||
|
|
}
|
||
|
|
if (rule.groupIds?.length) {
|
||
|
|
and.push({ groupLinks: { some: { groupId: { in: rule.groupIds } } } });
|
||
|
|
}
|
||
|
|
if (rule.sources?.length) {
|
||
|
|
and.push({
|
||
|
|
OR: [{ importSource: { in: rule.sources } }, { sourceRef: { in: rule.sources } }],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (
|
||
|
|
rule.moods?.length &&
|
||
|
|
!rule.owners?.length &&
|
||
|
|
!rule.groupIds?.length &&
|
||
|
|
!rule.sources?.length
|
||
|
|
) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (!and.length) continue;
|
||
|
|
parts.push(and.length === 1 ? and[0]! : { AND: and });
|
||
|
|
}
|
||
|
|
return orParts(parts);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function leadsDeepWhere(
|
||
|
|
actor: Pick<Actor, 'userId' | 'role' | 'leadsAccess' | 'teamPeerIds'>,
|
||
|
|
): Prisma.LeadWhereInput {
|
||
|
|
if (elevated(actor.role)) return {};
|
||
|
|
const section = actor.leadsAccess ?? { mode: 'all' as const, rules: [] };
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return {};
|
||
|
|
const parts: Prisma.LeadWhereInput[] = [];
|
||
|
|
for (const rule of section.rules ?? []) {
|
||
|
|
const and: Prisma.LeadWhereInput[] = [];
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
const ids = ownerIdList(rule, {
|
||
|
|
userId: actor.userId,
|
||
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
||
|
|
});
|
||
|
|
if (!ids.length) continue;
|
||
|
|
and.push(ids.length === 1 ? { assigneeId: ids[0]! } : { assigneeId: { in: ids } });
|
||
|
|
}
|
||
|
|
if (rule.stageIds?.length) {
|
||
|
|
and.push({ stageId: { in: rule.stageIds } });
|
||
|
|
}
|
||
|
|
if (rule.sources?.length) {
|
||
|
|
and.push({
|
||
|
|
OR: rule.sources.map((s) => ({
|
||
|
|
source: { equals: s, mode: 'insensitive' as const },
|
||
|
|
})),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (!and.length) continue;
|
||
|
|
parts.push(and.length === 1 ? and[0]! : { AND: and });
|
||
|
|
}
|
||
|
|
return orParts(parts);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function dealsDeepWhere(
|
||
|
|
actor: Pick<Actor, 'userId' | 'role' | 'salesAccess' | 'teamPeerIds'>,
|
||
|
|
): Prisma.DealWhereInput {
|
||
|
|
if (elevated(actor.role)) return {};
|
||
|
|
const section = actor.salesAccess ?? { mode: 'all' as const, rules: [] };
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return {};
|
||
|
|
const parts: Prisma.DealWhereInput[] = [];
|
||
|
|
for (const rule of section.rules ?? []) {
|
||
|
|
const and: Prisma.DealWhereInput[] = [];
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
const ids = ownerIdList(rule, {
|
||
|
|
userId: actor.userId,
|
||
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
||
|
|
});
|
||
|
|
if (!ids.length) continue;
|
||
|
|
and.push({
|
||
|
|
OR: [
|
||
|
|
{ ownerId: { in: ids } },
|
||
|
|
{ assignees: { some: { id: { in: ids } } } },
|
||
|
|
],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (rule.pipelineIds?.length) {
|
||
|
|
and.push({ pipelineId: { in: rule.pipelineIds } });
|
||
|
|
}
|
||
|
|
if (rule.stageIds?.length) {
|
||
|
|
and.push({ stageId: { in: rule.stageIds } });
|
||
|
|
}
|
||
|
|
if (!and.length) continue;
|
||
|
|
parts.push(and.length === 1 ? and[0]! : { AND: and });
|
||
|
|
}
|
||
|
|
return orParts(parts);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function activitiesDeepWhere(
|
||
|
|
actor: Pick<Actor, 'userId' | 'role' | 'activitiesAccess' | 'teamPeerIds'>,
|
||
|
|
): Prisma.ActivityWhereInput {
|
||
|
|
if (elevated(actor.role)) return {};
|
||
|
|
const section = actor.activitiesAccess ?? { mode: 'all' as const, rules: [] };
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return {};
|
||
|
|
const parts: Prisma.ActivityWhereInput[] = [];
|
||
|
|
for (const rule of section.rules ?? []) {
|
||
|
|
const and: Prisma.ActivityWhereInput[] = [];
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
const ids = ownerIdList(rule, {
|
||
|
|
userId: actor.userId,
|
||
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
||
|
|
});
|
||
|
|
if (!ids.length) continue;
|
||
|
|
and.push({
|
||
|
|
OR: [
|
||
|
|
{ assigneeId: { in: ids } },
|
||
|
|
{ assignees: { some: { id: { in: ids } } } },
|
||
|
|
{ participantIds: { hasSome: ids } },
|
||
|
|
],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (rule.boardIds?.length) {
|
||
|
|
and.push({ boardId: { in: rule.boardIds } });
|
||
|
|
}
|
||
|
|
if (rule.stageIds?.length) {
|
||
|
|
and.push({ stageId: { in: rule.stageIds } });
|
||
|
|
}
|
||
|
|
if (rule.typeIds?.length) {
|
||
|
|
and.push({ typeDefId: { in: rule.typeIds } });
|
||
|
|
}
|
||
|
|
if (!and.length) continue;
|
||
|
|
parts.push(and.length === 1 ? and[0]! : { AND: and });
|
||
|
|
}
|
||
|
|
return orParts(parts);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function notesDeepWhere(
|
||
|
|
actor: Pick<Actor, 'userId' | 'role' | 'notesAccess' | 'teamPeerIds'>,
|
||
|
|
): Prisma.NoteWhereInput {
|
||
|
|
const personalGate: Prisma.NoteWhereInput = {
|
||
|
|
OR: [
|
||
|
|
{ visibility: { not: 'personal' } },
|
||
|
|
{ visibility: 'personal', authorId: actor.userId },
|
||
|
|
],
|
||
|
|
};
|
||
|
|
if (elevated(actor.role)) return personalGate;
|
||
|
|
const section = actor.notesAccess ?? { mode: 'all' as const, rules: [] };
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return personalGate;
|
||
|
|
const parts: Prisma.NoteWhereInput[] = [];
|
||
|
|
for (const rule of section.rules ?? []) {
|
||
|
|
const and: Prisma.NoteWhereInput[] = [];
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
const ids = ownerIdList(rule, {
|
||
|
|
userId: actor.userId,
|
||
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
||
|
|
});
|
||
|
|
if (!ids.length) continue;
|
||
|
|
and.push({
|
||
|
|
OR: [
|
||
|
|
{ authorId: { in: ids } },
|
||
|
|
{
|
||
|
|
deal: {
|
||
|
|
OR: [
|
||
|
|
{ ownerId: { in: ids } },
|
||
|
|
{ assignees: { some: { id: { in: ids } } } },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{ person: { ownerId: { in: ids } } },
|
||
|
|
{ company: { ownerId: { in: ids } } },
|
||
|
|
],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (rule.sources?.length) {
|
||
|
|
and.push({ noteType: { in: rule.sources } });
|
||
|
|
}
|
||
|
|
if (!and.length) continue;
|
||
|
|
parts.push(and.length === 1 ? and[0]! : { AND: and });
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
AND: [personalGate, orParts(parts)],
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ticketsDeepWhere(
|
||
|
|
actor: Pick<Actor, 'userId' | 'role' | 'ticketsAccess' | 'teamPeerIds'>,
|
||
|
|
): Prisma.TicketWhereInput {
|
||
|
|
if (elevated(actor.role)) return {};
|
||
|
|
const section = actor.ticketsAccess ?? { mode: 'all' as const, rules: [] };
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return {};
|
||
|
|
const parts: Prisma.TicketWhereInput[] = [];
|
||
|
|
for (const rule of section.rules ?? []) {
|
||
|
|
const and: Prisma.TicketWhereInput[] = [];
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
const ids = ownerIdList(rule, {
|
||
|
|
userId: actor.userId,
|
||
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
||
|
|
});
|
||
|
|
if (!ids.length) continue;
|
||
|
|
and.push({
|
||
|
|
OR: [{ assigneeId: { in: ids } }, { reporterId: { in: ids } }],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (rule.sources?.length) {
|
||
|
|
and.push({
|
||
|
|
status: {
|
||
|
|
in: rule.sources as Array<'open' | 'in_progress' | 'waiting' | 'resolved' | 'closed'>,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (!and.length) continue;
|
||
|
|
parts.push(and.length === 1 ? and[0]! : { AND: and });
|
||
|
|
}
|
||
|
|
return orParts(parts);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function projectsDeepWhere(
|
||
|
|
actor: Pick<Actor, 'userId' | 'role' | 'projectsAccess' | 'teamPeerIds'>,
|
||
|
|
): Prisma.ProjectCardWhereInput {
|
||
|
|
if (elevated(actor.role)) return {};
|
||
|
|
const section = actor.projectsAccess ?? { mode: 'all' as const, rules: [] };
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return {};
|
||
|
|
const parts: Prisma.ProjectCardWhereInput[] = [];
|
||
|
|
for (const rule of section.rules ?? []) {
|
||
|
|
const and: Prisma.ProjectCardWhereInput[] = [];
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
const ids = ownerIdList(rule, {
|
||
|
|
userId: actor.userId,
|
||
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
||
|
|
});
|
||
|
|
if (!ids.length) continue;
|
||
|
|
and.push({
|
||
|
|
OR: [
|
||
|
|
{ ownerId: { in: ids } },
|
||
|
|
{ assignees: { some: { id: { in: ids } } } },
|
||
|
|
],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (rule.boardIds?.length) {
|
||
|
|
and.push({ boardId: { in: rule.boardIds } });
|
||
|
|
}
|
||
|
|
if (rule.stageIds?.length) {
|
||
|
|
and.push({ stageId: { in: rule.stageIds } });
|
||
|
|
}
|
||
|
|
if (!and.length) continue;
|
||
|
|
parts.push(and.length === 1 ? and[0]! : { AND: and });
|
||
|
|
}
|
||
|
|
return orParts(parts);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function routinesDeepWhere(
|
||
|
|
actor: Pick<Actor, 'userId' | 'role' | 'routinesAccess' | 'teamPeerIds'>,
|
||
|
|
): Prisma.RoutineTaskWhereInput {
|
||
|
|
if (elevated(actor.role)) return {};
|
||
|
|
const section = actor.routinesAccess ?? { mode: 'all' as const, rules: [] };
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return {};
|
||
|
|
const parts: Prisma.RoutineTaskWhereInput[] = [];
|
||
|
|
for (const rule of section.rules ?? []) {
|
||
|
|
const and: Prisma.RoutineTaskWhereInput[] = [];
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
const ids = ownerIdList(rule, {
|
||
|
|
userId: actor.userId,
|
||
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
||
|
|
});
|
||
|
|
if (!ids.length) continue;
|
||
|
|
and.push(ids.length === 1 ? { assigneeId: ids[0]! } : { assigneeId: { in: ids } });
|
||
|
|
}
|
||
|
|
if (rule.typeIds?.length) {
|
||
|
|
and.push({ categoryId: { in: rule.typeIds } });
|
||
|
|
}
|
||
|
|
if (!and.length) continue;
|
||
|
|
parts.push(and.length === 1 ? and[0]! : { AND: and });
|
||
|
|
}
|
||
|
|
return orParts(parts);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** اتحاد شناسهٔ owners در قوانین؛ null = بدون محدودیت مالکی (یا mode=all) */
|
||
|
|
export function sectionOwnerIdUnion(
|
||
|
|
section: SectionDataAccess | null | undefined,
|
||
|
|
ctx: { userId: string; teamPeerIds: string[] | null },
|
||
|
|
): string[] | null {
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return null;
|
||
|
|
const ids = new Set<string>();
|
||
|
|
let sawOwnerRule = false;
|
||
|
|
let sawNonOwnerRule = false;
|
||
|
|
for (const rule of section?.rules ?? []) {
|
||
|
|
if (rule.owners?.length) {
|
||
|
|
sawOwnerRule = true;
|
||
|
|
for (const id of ownerIdList(rule, ctx)) ids.add(id);
|
||
|
|
} else if (rule.groupIds?.length || rule.sources?.length || rule.moods?.length ||
|
||
|
|
rule.stageIds?.length || rule.pipelineIds?.length || rule.boardIds?.length ||
|
||
|
|
rule.typeIds?.length) {
|
||
|
|
sawNonOwnerRule = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (sawNonOwnerRule && !sawOwnerRule) return null;
|
||
|
|
if (!sawOwnerRule) return null;
|
||
|
|
return [...ids];
|
||
|
|
}
|
||
|
|
|
||
|
|
export function sectionNeedsTeamPeers(section: SectionDataAccess | null | undefined): boolean {
|
||
|
|
if (!sectionHasDataAccessLimits(section)) return false;
|
||
|
|
return (section?.rules ?? []).some((r) => r.owners?.includes('team'));
|
||
|
|
}
|
||
|
|
|
||
|
|
export function sanitizeDataAccessPolicyInput(raw: unknown): DataAccessPolicy {
|
||
|
|
return parseDataAccessPolicy(raw);
|
||
|
|
}
|