diff --git a/src/vocabulary/acl.ts b/src/vocabulary/acl.ts new file mode 100644 index 0000000..f6dd57a --- /dev/null +++ b/src/vocabulary/acl.ts @@ -0,0 +1,52 @@ +export const ACL = { + /** + * Allows access to a class of append operations on a resource, e.g., to add information, but not remove, information on HTTP POST, PATCH requests. + * + * @see https://solidproject.org/TR/wac#acl-mode-append + */ + Append: "http://www.w3.org/ns/auth/acl#Append", + + /** + * Allows access to any authenticated agent. + * + * @see https://solidproject.org/TR/wac#acl-agentclass-authenticated-agent + */ + AuthenticatedAgent: "http://www.w3.org/ns/auth/acl#AuthenticatedAgent", + + Authorization: "http://www.w3.org/ns/auth/acl#Authorization", + + accessTo: "http://www.w3.org/ns/auth/acl#accessTo", + + agent: "http://www.w3.org/ns/auth/acl#agent", + + agentClass: "http://www.w3.org/ns/auth/acl#agentClass", + + agentGroup: "http://www.w3.org/ns/auth/acl#agentGroup", + + /** + * Allows access to a class of read and write operations on an ACL resource associated with a resource. + * + * @see https://solidproject.org/TR/wac#acl-mode-control + */ + Control: "http://www.w3.org/ns/auth/acl#Control", + + default: "http://www.w3.org/ns/auth/acl#default", + + mode: "http://www.w3.org/ns/auth/acl#mode", + + origin: "http://www.w3.org/ns/auth/acl#origin", + + /** + * Allows access to a class of read operations on a resource, e.g., to view the contents of a resource on HTTP GET requests. + * + * @see https://solidproject.org/TR/wac#acl-mode-read + */ + Read: "http://www.w3.org/ns/auth/acl#Read", + + /** + * Allows access to a class of write operations on a resource, e.g., to create, delete or modify resources on HTTP PUT, POST, PATCH, DELETE requests. + * + * @see https://solidproject.org/TR/wac#acl-mode-write + */ + Write: "http://www.w3.org/ns/auth/acl#Write", +} as const; diff --git a/src/vocabulary/foaf.ts b/src/vocabulary/foaf.ts index c375e39..4c09ae3 100644 --- a/src/vocabulary/foaf.ts +++ b/src/vocabulary/foaf.ts @@ -5,4 +5,9 @@ export const FOAF = { email: "http://xmlns.com/foaf/0.1/email", homepage: "http://xmlns.com/foaf/0.1/homepage", knows: "http://xmlns.com/foaf/0.1/knows", + + /** + * @remarks [When used in WAC](https://solidproject.org/TR/wac#acl-agentclass-foaf-agent), allows access to any agent, i.e., the public. + */ + Agent: "http://xmlns.com/foaf/0.1/Agent", } as const; diff --git a/src/vocabulary/mod.ts b/src/vocabulary/mod.ts index 412818c..f84203b 100644 --- a/src/vocabulary/mod.ts +++ b/src/vocabulary/mod.ts @@ -1,3 +1,4 @@ +export * from "./acl.js" export * from "./acp.js" export * from "./dc.js" export * from "./foaf.js" diff --git a/src/vocabulary/vcard.ts b/src/vocabulary/vcard.ts index 19a3b39..c3701c0 100644 --- a/src/vocabulary/vcard.ts +++ b/src/vocabulary/vcard.ts @@ -3,6 +3,7 @@ export const VCARD = { Email: "http://www.w3.org/2006/vcard/ns#Email", email: "http://www.w3.org/2006/vcard/ns#email", hasEmail: "http://www.w3.org/2006/vcard/ns#hasEmail", + hasMember: "http://www.w3.org/2006/vcard/ns#hasMember", hasValue: "http://www.w3.org/2006/vcard/ns#hasValue", hasPhoto: "http://www.w3.org/2006/vcard/ns#hasPhoto", tel: "http://www.w3.org/2006/vcard/ns#tel", diff --git a/src/wac/AclResource.ts b/src/wac/AclResource.ts new file mode 100644 index 0000000..3e52410 --- /dev/null +++ b/src/wac/AclResource.ts @@ -0,0 +1,19 @@ +import { DatasetWrapper } from "@rdfjs/wrapper" +import { Authorization } from "./Authorization.js" +import { ACL } from "../vocabulary/mod.js" + +/** + * Represents an ACL Resource according to the Web Access Control specification. + * + * @see https://solidproject.org/TR/wac#acl-resource-representation + */ +class AclResource extends DatasetWrapper { + /** + * Gets all authorizations from the ACL Resource. + * + * @return {Iterable} Authorizations, any of which could permit an attempted access. + */ + get authorizations(): Iterable { + return this.instancesOf(ACL.Authorization, Authorization) + } +} diff --git a/src/wac/Authorization.ts b/src/wac/Authorization.ts new file mode 100644 index 0000000..fc481a6 --- /dev/null +++ b/src/wac/Authorization.ts @@ -0,0 +1,222 @@ +import { + NamedNodeAs, + NamedNodeFrom, + OptionalAs, + OptionalFrom, + SetFrom, + TermAs, + TermFrom, + TermWrapper +} from "@rdfjs/wrapper" +import { ACL, FOAF, RDF } from "../vocabulary/mod.js" +import { Group } from "./Group.js" + +/** + * Represents an Authorization according to the Web Access Control specification. + * + * Authorization is the most fundamental unit of access control describing access permissions granting to agents the ability to perform operations on resources. + * + * @see https://solidproject.org/TR/wac#authorization-rule + */ + +export class Authorization extends TermWrapper { + //#region Data + + //#region Access Objects + + /** + * Denotes the resource to which access is being granted. + * + * @see https://solidproject.org/TR/wac#acl-accessto + */ + get accessTo(): string | undefined { + return OptionalFrom.subjectPredicate(this, ACL.accessTo, NamedNodeAs.string) + } + + set accessTo(value: string | undefined) { + OptionalAs.object(this, ACL.accessTo, value, NamedNodeFrom.string) + } + + /** + * Denotes the container resource whose Authorization can be applied to a resource lower in the collection hierarchy. + * + * @see https://solidproject.org/TR/wac#acl-default + */ + get default(): string | undefined { + return OptionalFrom.subjectPredicate(this, ACL.default, NamedNodeAs.string) + } + + set default(value: string | undefined) { + OptionalAs.object(this, ACL.default, value, NamedNodeFrom.string) + } + + //#endregion + + //#region Access Modes + + /** + * Denotes a class of operations that the agents can perform on a resource. + * + * @see https://solidproject.org/TR/wac#acl-mode + */ + get mode(): Set { + return SetFrom.subjectPredicate(this, ACL.mode, NamedNodeAs.string, NamedNodeFrom.string) + } + + //#endregion + + //#region Access Subjects + + /** + * Denotes an agent being given the access permission. + * + * @see https://solidproject.org/TR/wac#acl-agent + */ + get agent(): Set { + return SetFrom.subjectPredicate(this, ACL.agent, NamedNodeAs.string, NamedNodeFrom.string) + } + + /** + * Denotes a class of agents being given the access permission. + * + * @see https://solidproject.org/TR/wac#acl-agentclass + */ + get agentClass(): Set { + return SetFrom.subjectPredicate(this, ACL.agentClass, NamedNodeAs.string, NamedNodeFrom.string) + } + + /** + * Denotes a group of agents being given the access permission. + * + * @see https://solidproject.org/TR/wac#acl-agentgroup + */ + get agentGroup(): Group | undefined { + return OptionalFrom.subjectPredicate(this, ACL.agentGroup, TermAs.instance(Group)) + } + + set agentGroup(value: Group | undefined) { + OptionalAs.object(this, ACL.agentGroup, value, TermFrom.instance) + } + + /** + * Denotes the origin of an HTTP request that is being given the access permission. + * + * @see https://solidproject.org/TR/wac#acl-origin + */ + get origin(): Set { + return SetFrom.subjectPredicate(this, ACL.origin, NamedNodeAs.string, NamedNodeFrom.string) + } + + //#endregion + + get type(): Set { + return SetFrom.subjectPredicate(this, RDF.type, NamedNodeAs.string, NamedNodeFrom.string) + } + + //#endregion + + //#region Computed + + /** + * @see https://solidproject.org/TR/wac#authorization-conformance + */ + get conforms(): boolean { + if (!this.type.has(ACL.Authorization)) { + return false + } + + if (this.accessTo === undefined || this.default === undefined) { + return false + } + + if (this.mode.size === 0) { + return false + } + + if (this.agent.size === 0 && (this.agentGroup == undefined || this.agentGroup.hasMember.size === 0) && this.agentClass.size === 0 && this.origin.size === 0) { + return false + } + + return true + } + + get accessibleToAny(): boolean { + return this.agentClass.has(FOAF.Agent) + } + + set accessibleToAny(value: boolean) { + this.overwrite(this.agentClass, FOAF.Agent, value) + } + + get accessibleToAuthenticated(): boolean { + return this.agentClass.has(ACL.AuthenticatedAgent) + } + + set accessibleToAuthenticated(value: boolean) { + this.overwrite(this.agentClass, ACL.AuthenticatedAgent, value) + } + + get canRead(): boolean { + return this.mode.has(ACL.Read) + } + + set canRead(value: boolean) { + this.overwrite(this.mode, ACL.Read, value) + } + + get canWrite(): boolean { + return this.mode.has(ACL.Write) + } + + set canWrite(value: boolean) { + this.overwrite(this.mode, ACL.Write, value) + } + + get canAppend(): boolean { + return this.mode.has(ACL.Append) + } + + set canAppend(value: boolean) { + this.overwrite(this.mode, ACL.Append, value) + } + + get canCreate(): boolean { + return this.canWrite || this.canAppend + } + + get canUpdate(): boolean { + return this.canCreate + } + + get canDelete(): boolean { + return this.canWrite + } + + set canDelete(value: boolean) { + this.canWrite = value + } + + get canReadWriteAcl(): boolean { + return this.mode.has(ACL.Control) + } + + set canReadWriteAcl(value: boolean) { + this.overwrite(this.mode, ACL.Control, value) + } + + //#endregion + + //#region Utilities + + private overwrite(set: Set, object: string, value: boolean) { + set.delete(object) + + if (!value) { + return + } + + set.add(object) + } + + //#endregion +} diff --git a/src/wac/Group.ts b/src/wac/Group.ts new file mode 100644 index 0000000..ddc828b --- /dev/null +++ b/src/wac/Group.ts @@ -0,0 +1,8 @@ +import { NamedNodeAs, NamedNodeFrom, SetFrom, TermWrapper } from "@rdfjs/wrapper" +import { VCARD } from "../vocabulary/mod.js" + +export class Group extends TermWrapper { + get hasMember(): Set { + return SetFrom.subjectPredicate(this, VCARD.hasMember, NamedNodeAs.string, NamedNodeFrom.string) + } +} diff --git a/src/wac/mod.ts b/src/wac/mod.ts new file mode 100644 index 0000000..bb814bb --- /dev/null +++ b/src/wac/mod.ts @@ -0,0 +1,3 @@ +export * from "./AclResource.js" +export * from "./Authorization.js" +export * from "./Group.js"