directus

Форк
0
/
merge-permissions-for-share.ts 
181 строка · 5.3 Кб
1
import type { Accountability, Filter, Permission, SchemaOverview } from '@directus/types';
2
import { assign, set, uniq } from 'lodash-es';
3
import { schemaPermissions } from '@directus/system-data';
4
import { mergePermissions } from './merge-permissions.js';
5
import { reduceSchema } from './reduce-schema.js';
6

7
export function mergePermissionsForShare(
8
	currentPermissions: Permission[],
9
	accountability: Accountability,
10
	schema: SchemaOverview,
11
): Permission[] {
12
	const defaults: Permission = {
13
		action: 'read',
14
		role: accountability.role,
15
		collection: '',
16
		permissions: {},
17
		validation: null,
18
		presets: null,
19
		fields: null,
20
	};
21

22
	const { collection, item } = accountability.share_scope!;
23

24
	const parentPrimaryKeyField = schema.collections[collection]!.primary;
25

26
	const reducedSchema = reduceSchema(schema, currentPermissions, ['read']);
27

28
	const relationalPermissions = traverse(reducedSchema, parentPrimaryKeyField, item, collection);
29

30
	const parentCollectionPermission: Permission = assign({}, defaults, {
31
		collection,
32
		permissions: {
33
			[parentPrimaryKeyField]: {
34
				_eq: item,
35
			},
36
		},
37
	});
38

39
	// All permissions that will be merged into the original permissions set
40
	const allGeneratedPermissions = [
41
		parentCollectionPermission,
42
		...relationalPermissions.map((generated) => assign({}, defaults, generated)),
43
		...schemaPermissions,
44
	];
45

46
	// All the collections that are touched through the relational tree from the current root collection, and the schema collections
47
	const allowedCollections = uniq(allGeneratedPermissions.map(({ collection }) => collection));
48

49
	const generatedPermissions: Permission[] = [];
50

51
	// Merge all the permissions that relate to the same collection with an _or (this allows you to properly retrieve)
52
	// the items of a collection if you entered that collection from multiple angles
53
	for (const collection of allowedCollections) {
54
		const permissionsForCollection = allGeneratedPermissions.filter(
55
			(permission) => permission.collection === collection,
56
		);
57

58
		if (permissionsForCollection.length > 0) {
59
			generatedPermissions.push(...mergePermissions('or', permissionsForCollection));
60
		} else {
61
			generatedPermissions.push(...permissionsForCollection);
62
		}
63
	}
64

65
	// Explicitly filter out permissions to collections unrelated to the root parent item.
66
	const limitedPermissions = currentPermissions.filter(
67
		({ action, collection }) => allowedCollections.includes(collection) && action === 'read',
68
	);
69

70
	return mergePermissions('and', limitedPermissions, generatedPermissions);
71
}
72

73
export function traverse(
74
	schema: SchemaOverview,
75
	rootItemPrimaryKeyField: string,
76
	rootItemPrimaryKey: string,
77
	currentCollection: string,
78
	parentCollections: string[] = [],
79
	path: string[] = [],
80
): Partial<Permission>[] {
81
	const permissions: Partial<Permission>[] = [];
82

83
	// If there's already a permissions rule for the collection we're currently checking, we'll shortcircuit.
84
	// This prevents infinite loop in recursive relationships, like articles->related_articles->articles, or
85
	// articles.author->users.avatar->files.created_by->users.avatar->files.created_by->🔁
86
	if (parentCollections.includes(currentCollection)) {
87
		return permissions;
88
	}
89

90
	const relationsInCollection = schema.relations.filter((relation) => {
91
		return relation.collection === currentCollection || relation.related_collection === currentCollection;
92
	});
93

94
	for (const relation of relationsInCollection) {
95
		let type;
96

97
		if (relation.related_collection === currentCollection) {
98
			type = 'o2m';
99
		} else if (!relation.related_collection) {
100
			type = 'a2o';
101
		} else {
102
			type = 'm2o';
103
		}
104

105
		if (type === 'o2m') {
106
			permissions.push({
107
				collection: relation.collection,
108
				permissions: getFilterForPath(type, [...path, relation.field], rootItemPrimaryKeyField, rootItemPrimaryKey),
109
			});
110

111
			permissions.push(
112
				...traverse(
113
					schema,
114
					rootItemPrimaryKeyField,
115
					rootItemPrimaryKey,
116
					relation.collection,
117
					[...parentCollections, currentCollection],
118
					[...path, relation.field],
119
				),
120
			);
121
		}
122

123
		if (type === 'a2o' && relation.meta?.one_allowed_collections) {
124
			for (const collection of relation.meta.one_allowed_collections) {
125
				permissions.push({
126
					collection,
127
					permissions: getFilterForPath(
128
						type,
129
						[...path, `$FOLLOW(${relation.collection},${relation.field},${relation.meta.one_collection_field})`],
130
						rootItemPrimaryKeyField,
131
						rootItemPrimaryKey,
132
					),
133
				});
134
			}
135
		}
136

137
		if (type === 'm2o') {
138
			permissions.push({
139
				collection: relation.related_collection!,
140
				permissions: getFilterForPath(
141
					type,
142
					[...path, `$FOLLOW(${relation.collection},${relation.field})`],
143
					rootItemPrimaryKeyField,
144
					rootItemPrimaryKey,
145
				),
146
			});
147

148
			if (relation.meta?.one_field) {
149
				permissions.push(
150
					...traverse(
151
						schema,
152
						rootItemPrimaryKeyField,
153
						rootItemPrimaryKey,
154
						relation.related_collection!,
155
						[...parentCollections, currentCollection],
156
						[...path, relation.meta?.one_field],
157
					),
158
				);
159
			}
160
		}
161
	}
162

163
	return permissions;
164
}
165

166
export function getFilterForPath(
167
	type: 'o2m' | 'm2o' | 'a2o',
168
	path: string[],
169
	rootPrimaryKeyField: string,
170
	rootPrimaryKey: string,
171
): Filter {
172
	const filter: Filter = {};
173

174
	if (type === 'm2o' || type === 'a2o') {
175
		set(filter, path.reverse(), { [rootPrimaryKeyField]: { _eq: rootPrimaryKey } });
176
	} else {
177
		set(filter, path.reverse(), { _eq: rootPrimaryKey });
178
	}
179

180
	return filter;
181
}
182

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.