import { Permission , Accountability , Filter } from '@directus/shared/types' ; import { SchemaOverview } from '../types' ; import { assign , set , uniq } from 'lodash' ; import { mergePermissions } from './merge-permissions' ; import { schemaPermissions } from '../database/system-data/app-access-permissions' ; import { reduceSchema } from './reduce-schema' ; export function mergePermissionsForShare ( currentPermissions , accountability , schema ) { const defaults = { action : 'read' , role : accountability . role , collection : '' , permissions : { } , validation : null , presets : null , fields : null , } ; const { collection , item } = accountability . share_scope ! ; const parentPrimaryKeyField = schema . collections [ collection ] . primary ; const reducedSchema = reduceSchema ( schema , currentPermissions , [ 'read' ] ) ; const relationalPermissions = traverse ( reducedSchema , parentPrimaryKeyField , item , collection ) ; const parentCollectionPermission = assign ( { } , defaults , { collection , permissions : { [ parentPrimaryKeyField ] : { _eq : item , } , } , } ) ; const allGeneratedPermissions = [ parentCollectionPermission , ... relationalPermissions . map ( ( generated ) => assign ( { } , defaults , generated ) ) , ... schemaPermissions , ] ; const allowedCollections = uniq ( allGeneratedPermissions . map ( ( { collection } ) => collection ) ) ; const generatedPermissions = [ ] ; for ( const collection of allowedCollections ) { const permissionsForCollection = allGeneratedPermissions . filter ( ( permission ) => permission . collection === collection ) ; if ( permissionsForCollection . length > 0 ) { generatedPermissions . push ( ... mergePermissions ( 'or' , permissionsForCollection ) ) ; } else { generatedPermissions . push ( ... permissionsForCollection ) ; } } const limitedPermissions = currentPermissions . filter ( ( { collection } ) => allowedCollections . includes ( collection ) ) ; return mergePermissions ( 'and' , limitedPermissions , generatedPermissions ) ; } export function traverse ( schema , rootItemPrimaryKeyField , rootItemPrimaryKey , currentCollection , parentCollections = [ ] , path = [ ] ) { const permissions = [ ] ; if ( parentCollections . includes ( currentCollection ) ) { return permissions ; } const relationsInCollection = schema . relations . filter ( ( relation ) => { return relation . collection === currentCollection || relation . related_collection === currentCollection ; } ) ; for ( const relation of relationsInCollection ) { let type ; if ( relation . related_collection === currentCollection ) { type = 'o2m' ; } else if ( ! relation . related_collection ) { type = 'a2o' ; } else { type = 'm2o' ; } if ( type === 'o2m' ) { permissions . push ( { collection : relation . collection , permissions : getFilterForPath ( type , [ ... path , relation . field ] , rootItemPrimaryKeyField , rootItemPrimaryKey ) , } ) ; permissions . push ( ... traverse ( schema , rootItemPrimaryKeyField , rootItemPrimaryKey , relation . collection , [ ... parentCollections , currentCollection ] , [ ... path , relation . field ] ) ) ; } if ( type === 'a2o' && relation . meta ?. one_allowed_collections ) { for ( const collection of relation . meta . one_allowed_collections ) { permissions . push ( { collection , permissions : getFilterForPath ( type , [ ... path , `$FOLLOW(${ relation . collection },${ relation . field },${ relation . meta . one_collection_field })` ] , rootItemPrimaryKeyField , rootItemPrimaryKey ) , } ) ; } } if ( type === 'm2o' ) { permissions . push ( { collection : relation . related_collection ! , permissions : getFilterForPath ( type , [ ... path , `$FOLLOW(${ relation . collection },${ relation . field })` ] , rootItemPrimaryKeyField , rootItemPrimaryKey ) , } ) ; if ( relation . meta ?. one_field ) { permissions . push ( ... traverse ( schema , rootItemPrimaryKeyField , rootItemPrimaryKey , relation . related_collection ! , [ ... parentCollections , currentCollection ] , [ ... path , relation . meta ?. one_field ] ) ) ; } } } return permissions ; } export function getFilterForPath ( type , path , rootPrimaryKeyField , rootPrimaryKey ) { const filter = { } ; if ( type === 'm2o' || type === 'a2o' ) { set ( filter , path . reverse ( ) , { [ rootPrimaryKeyField ] : { _eq : rootPrimaryKey } } ) ; } else { set ( filter , path . reverse ( ) , { _eq : rootPrimaryKey } ) ; } return filter ; }