1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | import { acl, rdf } from "./constants";
|
23 | import {
|
24 | fetchLitDataset,
|
25 | defaultFetchOptions,
|
26 | internal_fetchLitDatasetInfo,
|
27 | } from "./litDataset";
|
28 | import {
|
29 | DatasetInfo,
|
30 | unstable_AclDataset,
|
31 | unstable_hasAccessibleAcl,
|
32 | unstable_AclRule,
|
33 | unstable_AccessModes,
|
34 | Thing,
|
35 | IriString,
|
36 | unstable_Acl,
|
37 | } from "./interfaces";
|
38 | import { getThingAll } from "./thing";
|
39 | import { getIriOne, getIriAll } from "./thing/get";
|
40 |
|
41 |
|
42 | export async function internal_fetchResourceAcl(
|
43 | dataset: DatasetInfo,
|
44 | options: Partial<typeof defaultFetchOptions> = defaultFetchOptions
|
45 | ): Promise<unstable_AclDataset | null> {
|
46 | if (!unstable_hasAccessibleAcl(dataset)) {
|
47 | return null;
|
48 | }
|
49 |
|
50 | try {
|
51 | const aclLitDataset = await fetchLitDataset(
|
52 | dataset.datasetInfo.unstable_aclUrl,
|
53 | options
|
54 | );
|
55 | return Object.assign(aclLitDataset, {
|
56 | accessTo: dataset.datasetInfo.fetchedFrom,
|
57 | });
|
58 | } catch (e) {
|
59 |
|
60 |
|
61 |
|
62 | return null;
|
63 | }
|
64 | }
|
65 |
|
66 |
|
67 | export async function internal_fetchFallbackAcl(
|
68 | dataset: DatasetInfo & {
|
69 | datasetInfo: {
|
70 | unstable_aclUrl: Exclude<
|
71 | DatasetInfo["datasetInfo"]["unstable_aclUrl"],
|
72 | undefined
|
73 | >;
|
74 | };
|
75 | },
|
76 | options: Partial<typeof defaultFetchOptions> = defaultFetchOptions
|
77 | ): Promise<unstable_AclDataset | null> {
|
78 | const resourceUrl = new URL(dataset.datasetInfo.fetchedFrom);
|
79 | const resourcePath = resourceUrl.pathname;
|
80 |
|
81 |
|
82 |
|
83 |
|
84 | if (resourcePath === "/") {
|
85 |
|
86 | return null;
|
87 | }
|
88 |
|
89 | const containerPath = getContainerPath(resourcePath);
|
90 | const containerIri = new URL(containerPath, resourceUrl.origin).href;
|
91 | const containerInfo = await internal_fetchLitDatasetInfo(
|
92 | containerIri,
|
93 | options
|
94 | );
|
95 |
|
96 | if (!unstable_hasAccessibleAcl(containerInfo)) {
|
97 |
|
98 |
|
99 | return null;
|
100 | }
|
101 |
|
102 | const containerAcl = await internal_fetchResourceAcl(containerInfo, options);
|
103 | if (containerAcl === null) {
|
104 | return internal_fetchFallbackAcl(containerInfo, options);
|
105 | }
|
106 |
|
107 | return containerAcl;
|
108 | }
|
109 |
|
110 | function getContainerPath(resourcePath: string): string {
|
111 | const resourcePathWithoutTrailingSlash =
|
112 | resourcePath.substring(resourcePath.length - 1) === "/"
|
113 | ? resourcePath.substring(0, resourcePath.length - 1)
|
114 | : resourcePath;
|
115 |
|
116 | const containerPath =
|
117 | resourcePath.substring(
|
118 | 0,
|
119 | resourcePathWithoutTrailingSlash.lastIndexOf("/")
|
120 | ) + "/";
|
121 |
|
122 | return containerPath;
|
123 | }
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | export function unstable_hasResourceAcl<Dataset extends unstable_Acl>(
|
139 | dataset: Dataset
|
140 | ): dataset is Dataset & {
|
141 | acl: { resourceAcl: Exclude<unstable_Acl["acl"]["resourceAcl"], undefined> };
|
142 | } {
|
143 | return typeof dataset.acl.resourceAcl !== "undefined";
|
144 | }
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | export function unstable_getResourceAcl(
|
156 | dataset: unstable_Acl & {
|
157 | acl: {
|
158 | resourceAcl: Exclude<unstable_Acl["acl"]["resourceAcl"], undefined>;
|
159 | };
|
160 | }
|
161 | ): unstable_AclDataset;
|
162 | export function unstable_getResourceAcl(
|
163 | dataset: unstable_Acl
|
164 | ): unstable_AclDataset | null;
|
165 | export function unstable_getResourceAcl(
|
166 | dataset: unstable_Acl
|
167 | ): unstable_AclDataset | null {
|
168 | if (!unstable_hasResourceAcl(dataset)) {
|
169 | return null;
|
170 | }
|
171 | return dataset.acl.resourceAcl;
|
172 | }
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | export function unstable_hasFallbackAcl<Dataset extends unstable_Acl>(
|
187 | dataset: Dataset
|
188 | ): dataset is Dataset & {
|
189 | acl: { fallbackAcl: Exclude<unstable_Acl["acl"]["fallbackAcl"], null> };
|
190 | } {
|
191 | return dataset.acl.fallbackAcl !== null;
|
192 | }
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | export function unstable_getFallbackAcl(
|
204 | dataset: unstable_Acl & {
|
205 | acl: {
|
206 | fallbackAcl: Exclude<unstable_Acl["acl"]["fallbackAcl"], null>;
|
207 | };
|
208 | }
|
209 | ): unstable_AclDataset;
|
210 | export function unstable_getFallbackAcl(
|
211 | dataset: unstable_Acl
|
212 | ): unstable_AclDataset | null;
|
213 | export function unstable_getFallbackAcl(
|
214 | dataset: unstable_Acl
|
215 | ): unstable_AclDataset | null {
|
216 | if (!unstable_hasFallbackAcl(dataset)) {
|
217 | return null;
|
218 | }
|
219 | return dataset.acl.fallbackAcl;
|
220 | }
|
221 |
|
222 |
|
223 | export function internal_getAclRules(
|
224 | aclDataset: unstable_AclDataset
|
225 | ): unstable_AclRule[] {
|
226 | const things = getThingAll(aclDataset);
|
227 | return things.filter(isAclRule);
|
228 | }
|
229 |
|
230 | function isAclRule(thing: Thing): thing is unstable_AclRule {
|
231 | return getIriAll(thing, rdf.type).includes(acl.Authorization);
|
232 | }
|
233 |
|
234 |
|
235 | export function internal_getResourceAclRules(
|
236 | aclRules: unstable_AclRule[]
|
237 | ): unstable_AclRule[] {
|
238 | return aclRules.filter(isResourceAclRule);
|
239 | }
|
240 |
|
241 | function isResourceAclRule(aclRule: unstable_AclRule): boolean {
|
242 | return getIriOne(aclRule, acl.accessTo) !== null;
|
243 | }
|
244 |
|
245 |
|
246 | export function internal_getResourceAclRulesForResource(
|
247 | aclRules: unstable_AclRule[],
|
248 | resource: IriString
|
249 | ): unstable_AclRule[] {
|
250 | return aclRules.filter((rule) => appliesToResource(rule, resource));
|
251 | }
|
252 |
|
253 | function appliesToResource(
|
254 | aclRule: unstable_AclRule,
|
255 | resource: IriString
|
256 | ): boolean {
|
257 | return getIriAll(aclRule, acl.accessTo).includes(resource);
|
258 | }
|
259 |
|
260 |
|
261 | export function internal_getDefaultAclRules(
|
262 | aclRules: unstable_AclRule[]
|
263 | ): unstable_AclRule[] {
|
264 | return aclRules.filter(isDefaultAclRule);
|
265 | }
|
266 |
|
267 | function isDefaultAclRule(aclRule: unstable_AclRule): boolean {
|
268 | return getIriOne(aclRule, acl.default) !== null;
|
269 | }
|
270 |
|
271 |
|
272 | export function internal_getDefaultAclRulesForResource(
|
273 | aclRules: unstable_AclRule[],
|
274 | resource: IriString
|
275 | ): unstable_AclRule[] {
|
276 | return aclRules.filter((rule) => isDefaultForResource(rule, resource));
|
277 | }
|
278 |
|
279 | function isDefaultForResource(
|
280 | aclRule: unstable_AclRule,
|
281 | resource: IriString
|
282 | ): boolean {
|
283 | return getIriAll(aclRule, acl.default).includes(resource);
|
284 | }
|
285 |
|
286 |
|
287 | export function internal_getAccessModes(
|
288 | rule: unstable_AclRule
|
289 | ): unstable_AccessModes {
|
290 | const ruleAccessModes = getIriAll(rule, acl.mode);
|
291 | const writeAccess = ruleAccessModes.includes(accessModeIriStrings.write);
|
292 | return writeAccess
|
293 | ? {
|
294 | read: ruleAccessModes.includes(accessModeIriStrings.read),
|
295 | append: true,
|
296 | write: true,
|
297 | control: ruleAccessModes.includes(accessModeIriStrings.control),
|
298 | }
|
299 | : {
|
300 | read: ruleAccessModes.includes(accessModeIriStrings.read),
|
301 | append: ruleAccessModes.includes(accessModeIriStrings.append),
|
302 | write: false,
|
303 | control: ruleAccessModes.includes(accessModeIriStrings.control),
|
304 | };
|
305 | }
|
306 |
|
307 |
|
308 | export function internal_combineAccessModes(
|
309 | modes: unstable_AccessModes[]
|
310 | ): unstable_AccessModes {
|
311 | return modes.reduce(
|
312 | (accumulator, current) => {
|
313 | const writeAccess = accumulator.write || current.write;
|
314 | return writeAccess
|
315 | ? {
|
316 | read: accumulator.read || current.read,
|
317 | append: true,
|
318 | write: true,
|
319 | control: accumulator.control || current.control,
|
320 | }
|
321 | : {
|
322 | read: accumulator.read || current.read,
|
323 | append: accumulator.append || current.append,
|
324 | write: false,
|
325 | control: accumulator.control || current.control,
|
326 | };
|
327 | },
|
328 | { read: false, append: false, write: false, control: false }
|
329 | );
|
330 | }
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 | const accessModeIriStrings = {
|
337 | read: "http://www.w3.org/ns/auth/acl#Read",
|
338 | append: "http://www.w3.org/ns/auth/acl#Append",
|
339 | write: "http://www.w3.org/ns/auth/acl#Write",
|
340 | control: "http://www.w3.org/ns/auth/acl#Control",
|
341 | } as const;
|
342 |
|
343 | type AccessModeIriString = typeof accessModeIriStrings[keyof typeof accessModeIriStrings];
|