UNPKG

11.2 kBPlain TextView Raw
1/**
2 * Copyright 2020 Inrupt Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal in
6 * the Software without restriction, including without limitation the rights to use,
7 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8 * Software, and to permit persons to whom the Software is furnished to do so,
9 * subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 */
21
22import { acl, rdf } from "./constants";
23import {
24 fetchLitDataset,
25 defaultFetchOptions,
26 internal_fetchLitDatasetInfo,
27} from "./litDataset";
28import {
29 DatasetInfo,
30 unstable_AclDataset,
31 unstable_hasAccessibleAcl,
32 unstable_AclRule,
33 unstable_AccessModes,
34 Thing,
35 IriString,
36 unstable_Acl,
37} from "./interfaces";
38import { getThingAll } from "./thing";
39import { getIriOne, getIriAll } from "./thing/get";
40
41/** @internal */
42export 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 // Since a Solid server adds a `Link` header to an ACL even if that ACL does not exist,
60 // failure to fetch the ACL is expected to happen - we just return `null` and let callers deal
61 // with it.
62 return null;
63 }
64}
65
66/** @internal */
67export 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 // Note: we're currently assuming that the Origin is the root of the Pod. However, it is not yet
81 // set in stone that that will always be the case. We might need to check the Container's
82 // metadata at some point in time to check whether it is actually the root of the Pod.
83 // See: https://github.com/solid/specification/issues/153#issuecomment-624630022
84 if (resourcePath === "/") {
85 // We're already at the root, so there's no Container we can retrieve:
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 // If the current user does not have access to this Container's ACL,
98 // we cannot determine whether its ACL is the one that applies. Thus, return null:
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
110function 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 * Verify whether an ACL was found for the given LitDataset.
127 *
128 * A LitDataset fetched with [[unstable_fetchLitDatasetWithAcl]] _might_ have an ACL attached, but
129 * we cannot be sure: it might be that none exists for this specific Resource (in which case the
130 * fallback ACL applies), or the currently authenticated user (if any) might not have Control access
131 * to the fetched Resource.
132 *
133 * This function verifies that the LitDataset's ACL is accessible.
134 *
135 * @param dataset A [[LitDataset]] that might have an ACL attached.
136 * @returns Whether `dataset` has an ACL attached.
137 */
138export 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 * Access the ACL attached to a LitDataset.
148 *
149 * Given a LitDataset that has an ACL attached, this function will give you access to that ACL. To
150 * verify whether the ACL is available, see [[unstable_hasResourceAcl]].
151 *
152 * @param dataset A [[LitDataset]] with potentially an ACL attached.
153 * @returns The ACL, if available, and undefined if not.
154 */
155export function unstable_getResourceAcl(
156 dataset: unstable_Acl & {
157 acl: {
158 resourceAcl: Exclude<unstable_Acl["acl"]["resourceAcl"], undefined>;
159 };
160 }
161): unstable_AclDataset;
162export function unstable_getResourceAcl(
163 dataset: unstable_Acl
164): unstable_AclDataset | null;
165export 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 * Verify whether a fallback ACL was found for the given LitDataset.
176 *
177 * A LitDataset fetched with [[unstable_fetchLitDatasetWithAcl]] _might_ have a fallback ACL
178 * attached, but we cannot be sure: the currently authenticated user (if any) might not have Control
179 * access to one of the fetched Resource's Containers.
180 *
181 * This function verifies that the fallback ACL is accessible.
182 *
183 * @param dataset A [[LitDataset]] that might have a fallback ACL attached.
184 * @returns Whether `dataset` has a fallback ACL attached.
185 */
186export 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 * Access the fallback ACL attached to a LitDataset.
196 *
197 * Given a LitDataset that has a fallback ACL attached, this function will give you access to that
198 * ACL. To verify whether the fallback ACL is available, see [[unstable_hasFallbackAcl]].
199 *
200 * @param dataset A [[LitDataset]] with potentially a fallback ACL attached.
201 * @returns The fallback ACL, or null if it coult not be accessed.
202 */
203export function unstable_getFallbackAcl(
204 dataset: unstable_Acl & {
205 acl: {
206 fallbackAcl: Exclude<unstable_Acl["acl"]["fallbackAcl"], null>;
207 };
208 }
209): unstable_AclDataset;
210export function unstable_getFallbackAcl(
211 dataset: unstable_Acl
212): unstable_AclDataset | null;
213export 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/** @internal */
223export function internal_getAclRules(
224 aclDataset: unstable_AclDataset
225): unstable_AclRule[] {
226 const things = getThingAll(aclDataset);
227 return things.filter(isAclRule);
228}
229
230function isAclRule(thing: Thing): thing is unstable_AclRule {
231 return getIriAll(thing, rdf.type).includes(acl.Authorization);
232}
233
234/** @internal */
235export function internal_getResourceAclRules(
236 aclRules: unstable_AclRule[]
237): unstable_AclRule[] {
238 return aclRules.filter(isResourceAclRule);
239}
240
241function isResourceAclRule(aclRule: unstable_AclRule): boolean {
242 return getIriOne(aclRule, acl.accessTo) !== null;
243}
244
245/** @internal */
246export function internal_getResourceAclRulesForResource(
247 aclRules: unstable_AclRule[],
248 resource: IriString
249): unstable_AclRule[] {
250 return aclRules.filter((rule) => appliesToResource(rule, resource));
251}
252
253function appliesToResource(
254 aclRule: unstable_AclRule,
255 resource: IriString
256): boolean {
257 return getIriAll(aclRule, acl.accessTo).includes(resource);
258}
259
260/** @internal */
261export function internal_getDefaultAclRules(
262 aclRules: unstable_AclRule[]
263): unstable_AclRule[] {
264 return aclRules.filter(isDefaultAclRule);
265}
266
267function isDefaultAclRule(aclRule: unstable_AclRule): boolean {
268 return getIriOne(aclRule, acl.default) !== null;
269}
270
271/** @internal */
272export function internal_getDefaultAclRulesForResource(
273 aclRules: unstable_AclRule[],
274 resource: IriString
275): unstable_AclRule[] {
276 return aclRules.filter((rule) => isDefaultForResource(rule, resource));
277}
278
279function isDefaultForResource(
280 aclRule: unstable_AclRule,
281 resource: IriString
282): boolean {
283 return getIriAll(aclRule, acl.default).includes(resource);
284}
285
286/** @internal */
287export 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/** @internal */
308export 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 * IRIs of potential Access Modes
334 * @internal
335 */
336const 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/** @internal */
343type AccessModeIriString = typeof accessModeIriStrings[keyof typeof accessModeIriStrings];