ownacl
Version:
A library to manage access in solid pods.
165 lines (158 loc) • 4.68 kB
JavaScript
const rdf = require("rdflib");
const urllib = require("url");
const ns = require("solid-namespace")(rdf);
const { throwError, getDefaultAclLocation } = require("./utils.js");
const defaultOptions = { force: false };
async function read({ force } = defaultOptions) {
this.aclResource = this.resource.endsWith(".acl")
? this.resource
: this.resource + ".acl";
if (!this.body || force) {
let aclBody;
let url = this.aclResource;
while (!aclBody && url) {
aclBody = await this.fetcher
.load(url, { clearPreviousData: true })
.then(() => {
if (
url === this.resource + ".acl" ||
this.graph.any(
null,
ns.acl("default"),
rdf.sym(url.split(".acl")[0])
)
) {
this.body = rdf.serialize(
rdf.sym(url),
this.graph,
this.aclResource,
"text/turtle"
);
return this.body;
}
})
.catch((err) => {
const aclUrlObject = urllib.parse(url);
if (aclUrlObject.pathname !== "/.acl" && err.status === 404) {
url = getDefaultAclLocation(url);
} else if (err.status === 403 || err.status === 401) {
throwError(`Reading ${url} - Required access modes not granted`);
} else {
throwError(
`Reading ${url} - No acl file and no default configuration found, try adding an acl file to the requested url`
);
}
return undefined;
});
}
if (this.aclResource !== url) {
this.defaultAclResource = url;
} else {
this.defaultAclResource = undefined;
}
return aclBody;
} else {
return this.body;
}
}
function readAccessControl(options = defaultOptions) {
return this.read(options).then(() => {
return this.readEntities().then((entities) => {
entities.map((entity) => {
return this.readAccess(entity.name).then((access) => {
entity.access = access;
return entity;
});
});
return entities;
});
});
}
function readAccess(identifier, options = defaultOptions) {
return this.read(options).then(() => {
if (identifier.split("#")[0] === this.aclResource)
return this.graph
.each(rdf.sym(identifier), ns.acl("mode"))
.map((accessMode) => accessMode.value)
.sort();
const possibleIdentifier = this.graph
.statementsMatching(null, ns.acl("agent"), rdf.sym(identifier))
.concat(
this.graph.statementsMatching(
null,
ns.acl("agentClass"),
rdf.sym(identifier)
),
this.graph.statementsMatching(
null,
ns.acl("origin"),
rdf.sym(identifier)
)
);
const agentIdentifier =
possibleIdentifier.length > 0 ? possibleIdentifier[0].subject : null;
if (!agentIdentifier) {
throwError(
"Please specify a valid webId, url, agentClass or access identifier. Received: " +
identifier
);
}
return this.graph
.each(agentIdentifier, ns.acl("mode"))
.map((accessMode) => accessMode.value)
.sort();
});
}
function readEntities(options = defaultOptions) {
return this.read(options).then(() => {
const entities = this.graph
.statementsMatching(
null,
ns.acl("agent"),
null,
rdf.sym(
this.defaultAclResource ? this.defaultAclResource : this.aclResource
)
)
.concat(
this.graph.statementsMatching(
null,
ns.acl("origin"),
null,
rdf.sym(
this.defaultAclResource ? this.defaultAclResource : this.aclResource
)
),
this.graph.statementsMatching(
null,
ns.acl("agentClass"),
null,
rdf.sym(
this.defaultAclResource ? this.defaultAclResource : this.aclResource
)
)
)
.map((entity) => {
const type = entity.predicate.value.split("#")[1];
return {
name: entity.object.value,
type:
type === "agentClass"
? "AgentGroup"
: type.charAt(0).toUpperCase() + type.slice(1),
identifier: entity.subject.value,
};
})
.filter((entity) => !entity.name.startsWith("mailto:"))
.sort((entityA, entityB) =>
entityA.type === "AgentGroup" ? -1 : entityB.type === "AgentGroup" && 1
);
return entities;
});
}
module.exports = {
read,
readAccessControl,
readAccess,
readEntities,
};