Skip to content

Commit

Permalink
Add Access Control management functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Vinnl committed Oct 6, 2020
1 parent 1c9f1a1 commit b1bcc2a
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 4 deletions.
165 changes: 163 additions & 2 deletions src/acp/control.test.ts
Expand Up @@ -21,9 +21,22 @@

import { describe, it, expect } from "@jest/globals";

import { hasLinkedAcr, WithLinkedAcpAccessControl } from "./control";
import { acp } from "../constants";
import {
createAccessControl,
getAccessControl,
getAccessControlAll,
hasLinkedAcr,
removeAccessControl,
setAccessControl,
WithLinkedAcpAccessControl,
} from "./control";
import { acp, rdf } from "../constants";
import { WithAccessibleAcl, WithResourceInfo } from "../interfaces";
import { getIri } from "../thing/get";
import { createSolidDataset } from "../resource/solidDataset";
import { createThing, getThing, setThing } from "../thing/thing";
import { mockAcrFor } from "./mock";
import { setUrl } from "../thing/set";

describe("hasLinkedAcr", () => {
it("returns true if a Resource exposes a URL to an Access Control Resource", () => {
Expand Down Expand Up @@ -67,3 +80,151 @@ describe("hasLinkedAcr", () => {
expect(hasLinkedAcr(withLinkedAcr)).toBe(false);
});
});

describe("createAccessControl", () => {
it("sets the type of the new Access Control to acp:AccessControl", () => {
const newAccessControl = createAccessControl();

expect(getIri(newAccessControl, rdf.type)).toBe(acp.AccessControl);
});
});

describe("getAccessControl", () => {
it("returns the Access Control if found", () => {
const accessControlUrl =
"https://some.pod/access-control-resource.ttl#access-control";
const accessControl = setUrl(
createThing({ url: accessControlUrl }),
rdf.type,
acp.AccessControl
);
const accessControlResource = setThing(
mockAcrFor("https://some.pod/resource"),
accessControl
);

const foundAccessControl = getAccessControl(
accessControlResource,
accessControlUrl
);

expect(foundAccessControl).toEqual(accessControl);
});

it("returns null if the specified Thing is not an Access Control", () => {
const accessControlUrl =
"https://some.pod/access-control-resource.ttl#access-control";
const accessControl = createThing({ url: accessControlUrl });
const accessControlResource = setThing(
mockAcrFor("https://some.pod/resource"),
accessControl
);

const foundAccessControl = getAccessControl(
accessControlResource,
accessControlUrl
);

expect(foundAccessControl).toBeNull();
});

it("returns null if the Access Control could not be found", () => {
const accessControlUrl =
"https://some.pod/access-control-resource.ttl#access-control";
const accessControl = createThing({ url: accessControlUrl });
const accessControlResource = setThing(
mockAcrFor("https://some.pod/resource"),
accessControl
);

const foundAccessControl = getAccessControl(
accessControlResource,
"https://some-other.pod/access-control-resource.ttl#access-control"
);

expect(foundAccessControl).toBeNull();
});
});

describe("getAccessControlAll", () => {
it("returns all included Access Controls", () => {
const accessControl = setUrl(createThing(), rdf.type, acp.AccessControl);
const accessControlResource = setThing(
mockAcrFor("https://some.pod/resource"),
accessControl
);

const foundAccessControls = getAccessControlAll(accessControlResource);

expect(foundAccessControls).toEqual([accessControl]);
});

it("ignores Things that are not Access Controls", () => {
const accessControl = setUrl(createThing(), rdf.type, acp.AccessControl);
const notAnAccessControl = setUrl(
createThing(),
rdf.type,
"https://some.vocab/not-access-control"
);
let accessControlResource = mockAcrFor("https://some.pod/resource");
accessControlResource = setThing(accessControlResource, accessControl);
accessControlResource = setThing(accessControlResource, notAnAccessControl);

const foundAccessControls = getAccessControlAll(accessControlResource);

expect(foundAccessControls).toEqual([accessControl]);
});

it("returns an empty array if no Access Controls could be found", () => {
const accessControlResource = mockAcrFor("https://some.pod/resource");

const foundAccessControl = getAccessControlAll(accessControlResource);

expect(foundAccessControl).toEqual([]);
});
});

describe("setAccessControl", () => {
it("adds the given Access Control to the given Access Control Resource", () => {
const accessControlUrl =
"https://some.pod/access-control-resource.ttl#access-control";
const accessControl = setUrl(
createThing({ url: accessControlUrl }),
rdf.type,
acp.AccessControl
);
const accessControlResource = mockAcrFor("https://some.pod/resource");

const newAccessControlResource = setAccessControl(
accessControlResource,
accessControl
);

expect(getThing(newAccessControlResource, accessControlUrl)).toEqual(
accessControl
);
});
});

describe("removeAccessControl", () => {
it("removes the given Access Control from the given Access Control Resource", () => {
const accessControlUrl =
"https://some.pod/access-control-resource.ttl#access-control";
const accessControl = setUrl(
createThing({ url: accessControlUrl }),
rdf.type,
acp.AccessControl
);
const accessControlResource = setThing(
mockAcrFor("https://some.pod/resource"),
accessControl
);

const newAccessControlResource = removeAccessControl(
accessControlResource,
accessControl
);

expect(getThing(newAccessControlResource, accessControlUrl)).toBeNull();
});
});
111 changes: 109 additions & 2 deletions src/acp/control.ts
Expand Up @@ -19,8 +19,25 @@
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { acp } from "../constants";
import { hasResourceInfo, WithResourceInfo } from "../interfaces";
import { acp, rdf } from "../constants";
import {
hasResourceInfo,
SolidDataset,
Thing,
Url,
UrlString,
WithResourceInfo,
} from "../interfaces";
import { getIriAll } from "../thing/get";
import { setIri } from "../thing/set";
import {
createThing,
CreateThingOptions,
getThing,
getThingAll,
removeThing,
setThing,
} from "../thing/thing";

/**
* ```{note} The Web Access Control specification is not yet finalised. As such, this
Expand Down Expand Up @@ -48,6 +65,26 @@ export function hasLinkedAcr<Resource extends WithResourceInfo>(
);
}

/**
* ```{note} The Web Access Control specification is not yet finalised. As such, this
* function is still experimental and subject to change, even in a non-major release.
* ```
*
* An Access Control Resource, containing [[AccessControl]]s specifying which [[AccessPolicy]]'s
* apply to the Resource this Access Control Resource is linked to.
*/
export type AccessControlResource = SolidDataset & { accessTo: UrlString };

/**
* ```{note} The Web Access Control specification is not yet finalised. As such, this
* function is still experimental and subject to change, even in a non-major release.
* ```
*
* An Access Control, usually contained in an [[AccessControlResource]]. It describes which
* [[AccessPolicy]]'s apply to a Resource.
*/
export type AccessControl = Thing;

/**
* ```{note} The Web Access Control specification is not yet finalised. As such, this
* function is still experimental and subject to change, even in a non-major release.
Expand All @@ -67,3 +104,73 @@ export type WithLinkedAcpAccessControl<
};
};
};

/**
* Initialise a new [[AccessControl]].
*/
export function createAccessControl(
options?: Parameters<typeof createThing>[0]
): AccessControl {
let accessControl = createThing(options);
accessControl = setIri(accessControl, rdf.type, acp.AccessControl);
return accessControl;
}
/**
* Find an [[AccessControl]] with a given URL in a given Access Control Resource.
*
* @returns The requested Access Control, or `null` if it could not be found.
*/
export function getAccessControl(
accessControlResource: AccessControlResource,
url: Parameters<typeof getThing>[1],
options?: Parameters<typeof getThing>[2]
): AccessControl | null {
const foundThing = getThing(accessControlResource, url, options);
if (
foundThing === null ||
!getIriAll(foundThing, rdf.type).includes(acp.AccessControl)
) {
return null;
}

return foundThing;
}
/**
* Get all [[AccessControl]]s in a given Access Control Resource.
*/
export function getAccessControlAll(
accessControlResource: AccessControlResource,
options?: Parameters<typeof getThingAll>[1]
): AccessControl[] {
const foundThings = getThingAll(accessControlResource, options);

return foundThings.filter((foundThing) =>
getIriAll(foundThing, rdf.type).includes(acp.AccessControl)
);
}
/**
* Insert an [[AccessControl]] into an [[AccessControlResource]], replacing previous instances of that Access Control.
*
* @param accessControlResource The Access Control Resource to insert an Access Control into.
* @param accessControl The Access Control to insert into the given Access Control Resource.
* @returns A new Access Control Resource equal to the given Access Control Resource, but with the given Access Control.
*/
export function setAccessControl(
accessControlResource: AccessControlResource,
accessControl: AccessControl
): AccessControlResource {
return setThing(accessControlResource, accessControl);
}
/**
* Remove an [[AccessControl]] from an [[AccessControlResource]].
*
* @param accessControlResource The Access Control Resource to remove an Access Control from.
* @param accessControl The Access Control to remove from the given Access Control Resource.
* @returns A new Access Control Resource equal to the given Access Control Resource, excluding the given Access Control.
*/
export function removeAccessControl(
accessControlResource: AccessControlResource,
accessControl: AccessControl
): AccessControlResource {
return removeThing(accessControlResource, accessControl);
}
31 changes: 31 additions & 0 deletions src/acp/mock.test.ts
@@ -0,0 +1,31 @@
/**
* Copyright 2020 Inrupt Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { describe, it, expect } from "@jest/globals";
import { mockAcrFor } from "./mock";

describe("mockAcrFor", () => {
it("should attach the URL of the Resource it applies to", () => {
const mockedAcr = mockAcrFor("https://some.pod/resource");

expect(mockedAcr.accessTo).toBe("https://some.pod/resource");
});
});
48 changes: 48 additions & 0 deletions src/acp/mock.ts
@@ -0,0 +1,48 @@
/**
* Copyright 2020 Inrupt Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { UrlString, WithResourceInfo } from "../interfaces";
import { mockSolidDatasetFrom } from "../resource/mock";
import { getSourceUrl } from "../resource/resource";
import { AccessControlResource } from "./control";

/**
*
* ```{warning}
* Do not use this function in production code. For use in **unit tests** that require a
* [[AccessControlResource]].
* ```
*
* Initialises a new empty Access Control Resource for a given Resource for use
* in **unit tests**.
*
* @param resourceUrl The URL of the Resource to which the mocked ACR should apply.
* @returns The mocked empty Access Control Resource for the given Resource.
*/
export function mockAcrFor(resourceUrl: UrlString): AccessControlResource {
const acrUrl = new URL("access-control-resource", resourceUrl).href;
const acr: AccessControlResource = Object.assign(
mockSolidDatasetFrom(acrUrl),
{ accessTo: resourceUrl }
);

return acr;
}
1 change: 1 addition & 0 deletions src/constants.ts
Expand Up @@ -49,5 +49,6 @@ export const foaf = {
export const acp = {
AccessPolicyResource: "http://www.w3.org/ns/solid/acp#AccessPolicyResource",
AccessPolicy: "http://www.w3.org/ns/solid/acp#AccessPolicy",
AccessControl: "http://www.w3.org/ns/solid/acp#AccessControl",
accessControl: "http://www.w3.org/ns/solid/acp#accessControl",
} as const;

0 comments on commit b1bcc2a

Please sign in to comment.