Added getWac

main
jaxoncreed 2 years ago
parent 8632f0dfe3
commit 4d62c244dc
  1. 1200
      package-lock.json
  2. 5
      packages/solid/package.json
  3. 45
      packages/solid/src/.ldo/wac.context.ts
  4. 169
      packages/solid/src/.ldo/wac.schema.ts
  5. 19
      packages/solid/src/.ldo/wac.shapeTypes.ts
  6. 73
      packages/solid/src/.ldo/wac.typings.ts
  7. 23
      packages/solid/src/.shapes/wac.shex
  8. 2
      packages/solid/src/index.ts
  9. 13
      packages/solid/src/requester/requests/getAccessRules.ts
  10. 88
      packages/solid/src/requester/requests/setAccessRules.ts
  11. 42
      packages/solid/src/requester/results/error/HttpErrorResult.ts
  12. 11
      packages/solid/src/requester/results/success/AccessRule.ts
  13. 84
      packages/solid/src/resource/Resource.ts
  14. 19
      packages/solid/src/resource/wac/WacRule.ts
  15. 100
      packages/solid/src/resource/wac/getWacRule.ts
  16. 63
      packages/solid/src/resource/wac/getWacUri.ts
  17. 7
      packages/solid/src/resource/wac/results/GetWacRuleSuccess.ts
  18. 6
      packages/solid/src/resource/wac/results/GetWacUriSuccess.ts
  19. 7
      packages/solid/src/resource/wac/results/SetWacRuleSuccess.ts
  20. 0
      packages/solid/src/resource/wac/setWacRule.ts
  21. 33
      packages/solid/src/util/rdfUtils.ts
  22. 48
      packages/solid/test/Integration.test.ts
  23. 2
      packages/solid/test/solidServer.helper.ts

1200
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -40,14 +40,11 @@
"typedoc-plugin-markdown": "^3.17.1"
},
"dependencies": {
"@inrupt/solid-client": "^1.30.0",
"@ldo/dataset": "^0.0.1-alpha.17",
"@ldo/ldo": "^0.0.1-alpha.18",
"@ldo/rdf-utils": "^0.0.1-alpha.17",
"@types/parse-link-header": "^2.0.1",
"cross-fetch": "^3.1.6",
"http-link-header": "^1.1.1",
"ts-mixer": "^6.0.3"
"http-link-header": "^1.1.1"
},
"files": [
"dist",

@ -0,0 +1,45 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* wacContext: JSONLD Context for wac
* =============================================================================
*/
export const wacContext: ContextDefinition = {
type: {
"@id": "@type",
},
Authorization: "http://www.w3.org/ns/auth/acl#Authorization",
accessTo: {
"@id": "http://www.w3.org/ns/auth/acl#accessTo",
"@type": "@id",
},
default: {
"@id": "http://www.w3.org/ns/auth/acl#default",
"@type": "@id",
},
agent: {
"@id": "http://www.w3.org/ns/auth/acl#agent",
"@type": "@id",
"@container": "@set",
},
agentGroup: {
"@id": "http://www.w3.org/ns/auth/acl#agentGroup",
"@type": "@id",
"@container": "@set",
},
agentClass: {
"@id": "http://www.w3.org/ns/auth/acl#agentClass",
"@container": "@set",
},
AuthenticatedAgent: "http://www.w3.org/ns/auth/acl#AuthenticatedAgent",
Agent: "http://xmlns.com/foaf/0.1/Agent",
mode: {
"@id": "http://www.w3.org/ns/auth/acl#mode",
"@container": "@set",
},
Read: "http://www.w3.org/ns/auth/acl#Read",
Write: "http://www.w3.org/ns/auth/acl#Write",
Append: "http://www.w3.org/ns/auth/acl#Append",
Control: "http://www.w3.org/ns/auth/acl#Control",
};

@ -0,0 +1,169 @@
import { Schema } from "shexj";
/**
* =============================================================================
* wacSchema: ShexJ Schema for wac
* =============================================================================
*/
export const wacSchema: Schema = {
type: "Schema",
shapes: [
{
id: "http://www.w3.org/ns/auth/acls#Authorization",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
id: "http://www.w3.org/ns/auth/acls#AuthorizationShape",
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://www.w3.org/ns/auth/acl#Authorization"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Denotes this as an acl:Authorization",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#accessTo",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The subject of this authorization",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#default",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The container subject of this authorization",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#agent",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"An agent is a person, social entity or software identified by a URI, e.g., a WebID denotes an agent",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#agentGroup",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"Denotes a group of agents being given the access permission",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#agentClass",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/ns/auth/acl#AuthenticatedAgent",
"http://xmlns.com/foaf/0.1/Agent",
],
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"An agent class is a class of persons or entities identified by a URI.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#mode",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/ns/auth/acl#Read",
"http://www.w3.org/ns/auth/acl#Write",
"http://www.w3.org/ns/auth/acl#Append",
"http://www.w3.org/ns/auth/acl#Control",
],
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"Denotes a class of operations that the agents can perform on a resource.",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
],
};

@ -0,0 +1,19 @@
import { ShapeType } from "@ldo/ldo";
import { wacSchema } from "./wac.schema";
import { wacContext } from "./wac.context";
import { Authorization } from "./wac.typings";
/**
* =============================================================================
* LDO ShapeTypes wac
* =============================================================================
*/
/**
* Authorization ShapeType
*/
export const AuthorizationShapeType: ShapeType<Authorization> = {
schema: wacSchema,
shape: "http://www.w3.org/ns/auth/acls#Authorization",
context: wacContext,
};

@ -0,0 +1,73 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* Typescript Typings for wac
* =============================================================================
*/
/**
* Authorization Type
*/
export interface Authorization {
"@id"?: string;
"@context"?: ContextDefinition;
/**
* Denotes this as an acl:Authorization
*/
type: {
"@id": "Authorization";
};
/**
* The subject of this authorization
*/
accessTo?: {
"@id": string;
};
/**
* The container subject of this authorization
*/
default?: {
"@id": string;
};
/**
* An agent is a person, social entity or software identified by a URI, e.g., a WebID denotes an agent
*/
agent?: {
"@id": string;
}[];
/**
* Denotes a group of agents being given the access permission
*/
agentGroup?: {
"@id": string;
}[];
/**
* An agent class is a class of persons or entities identified by a URI.
*/
agentClass?: (
| {
"@id": "AuthenticatedAgent";
}
| {
"@id": "Agent";
}
)[];
/**
* Denotes a class of operations that the agents can perform on a resource.
*/
mode?: (
| {
"@id": "Read";
}
| {
"@id": "Write";
}
| {
"@id": "Append";
}
| {
"@id": "Control";
}
)[];
}

@ -0,0 +1,23 @@
PREFIX acl: <http://www.w3.org/ns/auth/acl#>
PREFIX acls: <http://www.w3.org/ns/auth/acls#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
acls:Authorization EXTRA a {
$acls:AuthorizationShape (
a [ acl:Authorization ]
// rdfs:comment "Denotes this as an acl:Authorization";
acl:accessTo IRI?
// rdfs:comment "The subject of this authorization";
acl:default IRI?
// rdfs:comment "The container subject of this authorization";
acl:agent IRI*
// rdfs:comment "An agent is a person, social entity or software identified by a URI, e.g., a WebID denotes an agent";
acl:agentGroup IRI*
// rdfs:comment "Denotes a group of agents being given the access permission";
acl:agentClass [ acl:AuthenticatedAgent foaf:Agent ]*
// rdfs:comment "An agent class is a class of persons or entities identified by a URI.";
acl:mode [ acl:Read acl:Write acl:Append acl:Control ]*
// rdfs:comment "Denotes a class of operations that the agents can perform on a resource.";
)
}

@ -13,9 +13,7 @@ export * from "./methods";
export * from "./requester/requests/checkRootContainer";
export * from "./requester/requests/createDataResource";
export * from "./requester/requests/deleteResource";
export * from "./requester/requests/getAccessRules";
export * from "./requester/requests/readResource";
export * from "./requester/requests/requestOptions";
export * from "./requester/requests/setAccessRules";
export * from "./requester/requests/updateDataResource";
export * from "./requester/requests/uploadResource";

@ -1,13 +0,0 @@
/* istanbul ignore file */
export async function getAccessRules(): Promise<undefined> {
throw new Error("Not Implemented");
// const [publicAccess, agentAccess] = await Promise.all([
// universalAccess.getPublicAccess(uri, { fetch }),
// universalAccess.getAgentAccessAll(uri, { fetch }),
// ]);
// if (agentAccess === null || publicAccess === null) {
// return new AccessRuleFetchError(uri);
// }
// return new AccessRuleResult(uri, publicAccess, agentAccess);
}

@ -1,88 +0,0 @@
/* istanbul ignore file */
import type { AclDataset, WithChangeLog } from "@inrupt/solid-client";
import {
getSolidDatasetWithAcl,
hasResourceAcl,
hasFallbackAcl,
hasAccessibleAcl,
createAclFromFallbackAcl,
getResourceAcl,
setAgentResourceAccess,
saveAclFor,
setPublicDefaultAccess,
setPublicResourceAccess,
setAgentDefaultAccess,
} from "@inrupt/solid-client";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import { isContainerUri } from "../../util/uriTypes";
import type { AccessRule } from "../results/success/AccessRule";
import type { SetAccessRuleSuccess } from "../results/success/AccessRule";
import { AccessRuleFetchError } from "../results/error/AccessControlError";
import type { BasicRequestOptions } from "./requestOptions";
export type SetAccessRulesResult =
| SetAccessRuleSuccess
| SetAccessRulesResultError;
export type SetAccessRulesResultError = AccessRuleFetchError;
export async function setAccessRules(
uri: string,
newAccessRules: AccessRule,
options?: BasicRequestOptions,
): Promise<SetAccessRulesResult> {
console.warn("Access Control is stil underdeveloped. Use with caution.");
const fetch = guaranteeFetch(options?.fetch);
const isContainer = isContainerUri(uri);
// Code Copied from https://docs.inrupt.com/developer-tools/javascript/client-libraries/tutorial/manage-wac/
// Fetch the SolidDataset and its associated ACLs, if available:
const myDatasetWithAcl = await getSolidDatasetWithAcl(uri, { fetch });
// Obtain the SolidDataset's own ACL, if available,
// or initialise a new one, if possible:
let resourceAcl;
if (!hasResourceAcl(myDatasetWithAcl)) {
if (!hasAccessibleAcl(myDatasetWithAcl)) {
return new AccessRuleFetchError(
uri,
"The current user does not have permission to change access rights to this Resource.",
);
}
if (!hasFallbackAcl(myDatasetWithAcl)) {
return new AccessRuleFetchError(
"The current user does not have permission to see who currently has access to this Resource.",
);
}
resourceAcl = createAclFromFallbackAcl(myDatasetWithAcl);
} else {
resourceAcl = getResourceAcl(myDatasetWithAcl);
}
// Give someone Control access to the given Resource:
let updatedAcl: AclDataset & WithChangeLog = resourceAcl;
if (newAccessRules.public) {
if (isContainer) {
updatedAcl = setPublicDefaultAccess(updatedAcl, newAccessRules.public);
} else {
updatedAcl = setPublicResourceAccess(updatedAcl, newAccessRules.public);
}
}
if (newAccessRules.agent) {
const setAgentAccess = isContainer
? setAgentDefaultAccess
: setAgentResourceAccess;
Object.entries(newAccessRules.agent).forEach(([webId, rules]) => {
updatedAcl = setAgentAccess(updatedAcl, webId, rules);
});
}
// Now save the ACL:
await saveAclFor(myDatasetWithAcl, updatedAcl, { fetch });
return {
isError: false,
uri,
type: "setAccessRuleSuccess",
};
}

@ -6,7 +6,8 @@ import { ResourceError } from "./ErrorResult";
export type HttpErrorResultType =
| ServerHttpError
| UnexpectedHttpError
| UnauthenticatedHttpError;
| UnauthenticatedHttpError
| UnauthorizedHttpError;
/**
* An error caused by an HTTP request
@ -70,6 +71,9 @@ export abstract class HttpErrorResult extends ResourceError {
if (UnauthenticatedHttpError.is(response)) {
return new UnauthenticatedHttpError(uri, response);
}
if (UnauthorizedHttpError.is(response)) {
return new UnauthorizedHttpError(uri, response);
}
if (HttpErrorResult.isnt(response)) {
return new UnexpectedHttpError(uri, response);
}
@ -102,6 +106,42 @@ export class UnauthenticatedHttpError extends HttpErrorResult {
}
}
/**
* An UnauthenticatedHttpError triggers when a Solid server returns a 403 status
* indicating that the request is not authorized.
*/
export class UnauthorizedHttpError extends HttpErrorResult {
readonly type = "unauthorizedError" as const;
/**
* Indicates if a specific response constitutes an UnauthenticatedHttpError
* @param response - The request response
* @returns true if this response constitutes an UnauthenticatedHttpError
*/
static is(response: Response) {
return response.status === 403;
}
}
/**
* An NotFoundHttpError triggers when a Solid server returns a 404 status. This
* error is not returned in most cases as a "absent" resource is not considered
* an error, but it is thrown while trying for find a WAC rule for a resource
* that does not exist.
*/
export class NotFoundHttpError extends HttpErrorResult {
readonly type = "notFoundError" as const;
/**
* Indicates if a specific response constitutes an NotFoundHttpError
* @param response - The request response
* @returns true if this response constitutes an NotFoundHttpError
*/
static is(response: Response) {
return response.status === 404;
}
}
/**
* A ServerHttpError triggers when a Solid server returns a 5XX status,
* indicating that an error happened on the server.

@ -1,11 +0,0 @@
import type { Access } from "@inrupt/solid-client";
import type { ResourceSuccess } from "./SuccessResult";
export interface AccessRule {
public?: Access;
agent?: Record<string, Access>;
}
export interface SetAccessRuleSuccess extends ResourceSuccess {
type: "setAccessRuleSuccess";
}

@ -11,9 +11,6 @@ import type {
} from "../requester/requests/readResource";
import type { BatchedRequester } from "../requester/BatchedRequester";
import type { CheckRootResultError } from "../requester/requests/checkRootContainer";
import type { AccessRule } from "../requester/results/success/AccessRule";
import type { SetAccessRulesResult } from "../requester/requests/setAccessRules";
import { setAccessRules } from "../requester/requests/setAccessRules";
import type TypedEmitter from "typed-emitter";
import EventEmitter from "events";
import { getParentUri } from "../util/rdfUtils";
@ -28,6 +25,10 @@ import type { CreateSuccess } from "../requester/results/success/CreateSuccess";
import type { ResourceResult } from "./resourceResult/ResourceResult";
import type { Container } from "./Container";
import type { Leaf } from "./Leaf";
import type { WacRule } from "./wac/WacRule";
import type { GetWacUriError, GetWacUriResult } from "./wac/getWacUri";
import { getWacUri } from "./wac/getWacUri";
import { getWacRuleWithAclUri, type GetWacRuleResult } from "./wac/getWacRule";
/**
* Statuses shared between both Leaf and Container
@ -79,6 +80,18 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
*/
protected absent: boolean | undefined;
/**
* @internal
* If a wac uri is fetched, it is cached here
*/
protected wacUri?: string;
/**
* @internal
* If a wac rule was fetched, it is cached here
*/
protected wacRule?: WacRule;
/**
* @param context - SolidLdoDatasetContext for the parent dataset
*/
@ -510,18 +523,61 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
*/
abstract getRootContainer(): Promise<Container | CheckRootResultError>;
// Access Rules Methods
// async getAccessRules(): Promise<AccessRuleResult | AccessRuleFetchError> {
// return getAccessRules({ uri: this.uri, fetch: this.context.fetch });
// }
/* istanbul ignore next */
async setAccessRules(
newAccessRules: AccessRule,
): Promise<ResourceResult<SetAccessRulesResult, Leaf | Container>> {
const result = await setAccessRules(this.uri, newAccessRules, {
/**
* ===========================================================================
* WEB ACCESS CONTROL METHODS
* ===========================================================================
*/
protected async getWacUri(options?: {
ignoreCache: boolean;
}): Promise<GetWacUriResult> {
// Get the wacUri if not already present
if (!options?.ignoreCache && this.wacUri) {
return {
type: "getWacUriSuccess",
wacUri: this.wacUri,
isError: false,
uri: this.uri,
};
}
const wacUriResult = await getWacUri(this.uri, {
fetch: this.context.fetch,
});
if (wacUriResult.isError) {
return wacUriResult;
}
this.wacUri = wacUriResult.wacUri;
return wacUriResult;
}
async getWac(options?: {
ignoreCache: boolean;
}): Promise<GetWacUriError | GetWacRuleResult> {
// Return the wac rule if it's already cached
if (!options?.ignoreCache && this.wacRule) {
return {
type: "getWacRuleSuccess",
uri: this.uri,
isError: false,
wacRule: this.wacRule,
};
}
// Get the wac uri
const wacUriResult = await this.getWacUri(options);
if (wacUriResult.isError) {
return wacUriResult;
}
// Get the wac rule
return getWacRuleWithAclUri(wacUriResult.wacUri, {
fetch: this.context.fetch,
});
if (result.isError) return result;
return { ...result, resource: this as unknown as Leaf | Container };
}
// async setWac(wacRule: WacRule): Promise<> {
// throw new Error("Not Implemented");
// }
}

@ -0,0 +1,19 @@
export interface AccessModeList {
read: boolean;
append: boolean;
write: boolean;
control: boolean;
}
export interface WacRule {
public: AccessModeList;
authenticated: AccessModeList;
agent: Record<string, AccessModeList>;
}
// export interface SetWacRule {
// ruleFor: Resource;
// public?: AccessModeList;
// authenticated?: AccessModeList;
// agent?: Record<string, AccessModeList>;
// }

@ -0,0 +1,100 @@
import type { GetWacRuleSuccess } from "./results/GetWacRuleSuccess";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import type { BasicRequestOptions } from "../../requester/requests/requestOptions";
import type { HttpErrorResultType } from "../../requester/results/error/HttpErrorResult";
import { HttpErrorResult } from "../../requester/results/error/HttpErrorResult";
import type { NoncompliantPodError } from "../../requester/results/error/NoncompliantPodError";
import type { UnexpectedResourceError } from "../../requester/results/error/ErrorResult";
import { rawTurtleToDataset } from "../../util/rdfUtils";
import { AuthorizationShapeType } from "../../.ldo/wac.shapeTypes";
import type { AccessModeList, WacRule } from "./WacRule";
import type { Authorization } from "../../.ldo/wac.typings";
export type GetWacRuleError =
| HttpErrorResultType
| NoncompliantPodError
| UnexpectedResourceError;
export type GetWacRuleResult = GetWacRuleSuccess | GetWacRuleError;
export async function getWacRuleWithAclUri(
aclUri: string,
options?: BasicRequestOptions,
): Promise<GetWacRuleResult> {
const fetch = guaranteeFetch(options?.fetch);
const response = await fetch(aclUri);
const errorResult = HttpErrorResult.checkResponse(aclUri, response);
if (errorResult) return errorResult;
// Parse Turtle
const rawTurtle = await response.text();
const rawTurtleResult = await rawTurtleToDataset(rawTurtle, aclUri);
if (rawTurtleResult.isError) return rawTurtleResult;
const dataset = rawTurtleResult.dataset;
const authorizations = dataset
.usingType(AuthorizationShapeType)
.matchSubject(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
"http://www.w3.org/ns/auth/acl#Authorization",
);
const wacRule: WacRule = {
public: {
read: false,
write: false,
append: false,
control: false,
},
authenticated: {
read: false,
write: false,
append: false,
control: false,
},
agent: {},
};
function applyAccessModesToList(
accessModeList: AccessModeList,
authorization: Authorization,
): void {
authorization.mode?.forEach((mode) => {
accessModeList[mode["@id"].toLowerCase()] = true;
});
}
authorizations.forEach((authorization) => {
if (
authorization.agentClass?.some(
(agentClass) => agentClass["@id"] === "Agent",
)
) {
applyAccessModesToList(wacRule.public, authorization);
applyAccessModesToList(wacRule.authenticated, authorization);
}
if (
authorization.agentClass?.some(
(agentClass) => agentClass["@id"] === "AuthenticatedAgent",
)
) {
applyAccessModesToList(wacRule.authenticated, authorization);
}
authorization.agent?.forEach((agent) => {
if (!wacRule.agent[agent["@id"]]) {
wacRule.agent[agent["@id"]] = {
read: false,
write: false,
append: false,
control: false,
};
}
applyAccessModesToList(wacRule.agent[agent["@id"]], authorization);
});
});
return {
type: "getWacRuleSuccess",
uri: aclUri,
isError: false,
wacRule,
};
}

@ -0,0 +1,63 @@
import type { GetWacUriSuccess } from "./results/GetWacUriSuccess";
import type { HttpErrorResultType } from "../../requester/results/error/HttpErrorResult";
import {
HttpErrorResult,
NotFoundHttpError,
} from "../../requester/results/error/HttpErrorResult";
import { UnexpectedResourceError } from "../../requester/results/error/ErrorResult";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import type { BasicRequestOptions } from "../../requester/requests/requestOptions";
import { NoncompliantPodError } from "../../requester/results/error/NoncompliantPodError";
import { parse as parseLinkHeader } from "http-link-header";
export type GetWacUriError =
| HttpErrorResultType
| NotFoundHttpError
| NoncompliantPodError
| UnexpectedResourceError;
export type GetWacUriResult = GetWacUriSuccess | GetWacUriError;
export async function getWacUri(
resourceUri: string,
options?: BasicRequestOptions,
): Promise<GetWacUriResult> {
try {
const fetch = guaranteeFetch(options?.fetch);
const response = await fetch(resourceUri, {
method: "head",
});
const errorResult = HttpErrorResult.checkResponse(resourceUri, response);
if (errorResult) return errorResult;
if (NotFoundHttpError.is(response)) {
return new NotFoundHttpError(
resourceUri,
response,
"Could not get access control rules because the resource does not exist.",
);
}
// Get the URI from the link header
const linkHeader = response.headers.get("link");
if (!linkHeader) {
return new NoncompliantPodError(
resourceUri,
"No link header present in request.",
);
}
const parsedLinkHeader = parseLinkHeader(linkHeader);
const aclUris = parsedLinkHeader.get("rel", "acl");
if (aclUris.length !== 1) {
return new NoncompliantPodError(
resourceUri,
`There must be one link with a rel="acl"`,
);
}
return {
type: "getWacUriSuccess",
isError: false,
uri: resourceUri,
wacUri: aclUris[0].uri,
};
} catch (err: unknown) {
return UnexpectedResourceError.fromThrown(resourceUri, err);
}
}

@ -0,0 +1,7 @@
import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult";
import type { WacRule } from "../WacRule";
export interface GetWacRuleSuccess extends ResourceSuccess {
type: "getWacRuleSuccess";
wacRule: WacRule;
}

@ -0,0 +1,6 @@
import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult";
export interface GetWacUriSuccess extends ResourceSuccess {
type: "getWacUriSuccess";
wacUri: string;
}

@ -0,0 +1,7 @@
import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult";
import type { WacRule } from "../WacRule";
export interface SetWacRuleSuccess extends ResourceSuccess {
type: "setWacRuleSuccess";
wacRule: WacRule;
}

@ -1,3 +1,4 @@
import type { LdoDataset } from "@ldo/ldo";
import { parseRdf } from "@ldo/ldo";
import { namedNode, quad as createQuad } from "@rdfjs/data-model";
import type { Dataset } from "@rdfjs/types";
@ -115,11 +116,29 @@ export async function addRawTurtleToDataset(
dataset: Dataset,
baseUri: string,
): Promise<undefined | NoncompliantPodError> {
let loadedDataset: Dataset;
const rawTurtleResult = await rawTurtleToDataset(rawTurtle, baseUri);
if (rawTurtleResult.isError) return rawTurtleResult;
const loadedDataset = rawTurtleResult.dataset;
const graphNode = namedNode(baseUri);
// Destroy all triples that were once a part of this resouce
dataset.deleteMatches(undefined, undefined, undefined, graphNode);
// Add the triples from the fetched item
dataset.addAll(
loadedDataset.map((quad) =>
createQuad(quad.subject, quad.predicate, quad.object, graphNode),
),
);
}
export async function rawTurtleToDataset(
rawTurtle: string,
baseUri: string,
): Promise<{ isError: false; dataset: LdoDataset } | NoncompliantPodError> {
try {
loadedDataset = await parseRdf(rawTurtle, {
const loadedDataset = await parseRdf(rawTurtle, {
baseIRI: baseUri,
});
return { isError: false, dataset: loadedDataset };
} catch (err) {
const error = UnexpectedResourceError.fromThrown(baseUri, err);
return new NoncompliantPodError(
@ -127,14 +146,4 @@ export async function addRawTurtleToDataset(
`Request returned noncompliant turtle: ${error.message}`,
);
}
const graphNode = namedNode(baseUri);
// Destroy all triples that were once a part of this resouce
dataset.deleteMatches(undefined, undefined, undefined, graphNode);
// Add the triples from the fetched item
dataset.addAll(
loadedDataset.map((quad) =>
createQuad(quad.subject, quad.predicate, quad.object, graphNode),
),
);
}

@ -10,6 +10,7 @@ import type {
import { changeData, commitData, createSolidLdoDataset } from "../src";
import {
ROOT_CONTAINER,
WEB_ID,
createApp,
getAuthenticatedFetch,
} from "./solidServer.helper";
@ -45,7 +46,7 @@ import type {
UnexpectedHttpError,
} from "../src/requester/results/error/HttpErrorResult";
import type { NoncompliantPodError } from "../src/requester/results/error/NoncompliantPodError";
import { wait } from "./utils.helper";
import type { GetWacRuleSuccess } from "../src/resource/wac/results/GetWacRuleSuccess";
const TEST_CONTAINER_SLUG = "test_ldo/";
const TEST_CONTAINER_URI =
@ -92,6 +93,12 @@ const TEST_CONTAINER_TTL = `@prefix dc: <http://purl.org/dc/terms/>.
posix:size 522.
<sample.txt> posix:mtime 1697810234;
posix:size 10.`;
const TEST_CONTAINER_ACL_URI = `${TEST_CONTAINER_URI}.acl`;
const TEST_CONTAINER_ACL = `<#b30e3fd1-b5a8-4763-ad9d-e95de9cf7933> a <http://www.w3.org/ns/auth/acl#Authorization>;
<http://www.w3.org/ns/auth/acl#accessTo> <${TEST_CONTAINER_URI}>;
<http://www.w3.org/ns/auth/acl#default> <${TEST_CONTAINER_URI}>;
<http://www.w3.org/ns/auth/acl#mode> <http://www.w3.org/ns/auth/acl#Read>, <http://www.w3.org/ns/auth/acl#Write>, <http://www.w3.org/ns/auth/acl#Append>, <http://www.w3.org/ns/auth/acl#Control>;
<http://www.w3.org/ns/auth/acl#agent> <${WEB_ID}>.`;
async function testRequestLoads<ReturnVal>(
request: () => Promise<ReturnVal>,
@ -167,6 +174,13 @@ describe("Integration", () => {
slug: TEST_CONTAINER_SLUG,
},
});
await authFetch(TEST_CONTAINER_ACL_URI, {
method: "PUT",
headers: {
"content-type": "text/turtle",
},
body: TEST_CONTAINER_ACL,
});
await Promise.all([
authFetch(TEST_CONTAINER_URI, {
method: "POST",
@ -1610,4 +1624,36 @@ describe("Integration", () => {
).toBe(true);
});
});
/**
* ===========================================================================
* ACCESS CONTROL
* ===========================================================================
*/
describe("getWacRule", () => {
it("Fetches a wac rules for a container that has a corresponding acl", async () => {
const container = solidLdoDataset.getResource(TEST_CONTAINER_URI);
const wacResult = await container.getWac();
expect(wacResult.isError).toBe(false);
const wacSuccess = wacResult as GetWacRuleSuccess;
expect(wacSuccess.wacRule.public).toEqual({
read: false,
write: false,
append: false,
control: false,
});
expect(wacSuccess.wacRule.authenticated).toEqual({
read: false,
write: false,
append: false,
control: false,
});
expect(wacSuccess.wacRule.agent[WEB_ID]).toEqual({
read: true,
write: true,
append: true,
control: true,
});
});
});
});

@ -23,6 +23,8 @@ const config = [
export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/";
export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "";
export const ROOT_CONTAINER = `${SERVER_DOMAIN}${ROOT_ROUTE}`;
export const WEB_ID =
process.env.WEB_ID || `${SERVER_DOMAIN}example/profile/card#me`;
// Use an increased timeout, since the CSS server takes too much setup time.
jest.setTimeout(40_000);

Loading…
Cancel
Save