solid-ui
Version:
UI library for writing Solid read-write-web applications
517 lines (492 loc) • 19.5 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ACLToString = ACLToString;
exports.ACLbyCombination = ACLbyCombination;
exports.ACLunion = ACLunion;
exports.adoptACLDefault = adoptACLDefault;
exports.comboToString = comboToString;
exports.fixIndividualACL = fixIndividualACL;
exports.fixIndividualCardACL = fixIndividualCardACL;
exports.getACL = getACL;
exports.getACLorDefault = getACLorDefault;
exports.getProspectiveHolder = getProspectiveHolder;
exports.loadUnionACL = loadUnionACL;
exports.makeACLGraph = makeACLGraph;
exports.makeACLGraphbyCombo = makeACLGraphbyCombo;
exports.makeACLString = makeACLString;
exports.putACLObject = putACLObject;
exports.putACLbyCombo = putACLbyCombo;
exports.readACL = readACL;
exports.sameACL = sameACL;
exports.setACL = setACL;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var ns = _interopRequireWildcard(require("../ns"));
var _solidLogic = require("solid-logic");
var utils = _interopRequireWildcard(require("../utils"));
var debug = _interopRequireWildcard(require("../debug"));
var _rdflib = require("rdflib");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
/**
* Non-UI functions for access control.
* See https://github.com/solidos/web-access-control-spec
* for the spec that defines how ACL documents work.
* @packageDocumentation
*/
var kb = _solidLogic.solidLogicSingleton.store;
/**
* Take the "default" ACL and convert it into the equivalent ACL
* which the resource would have had. Return it as a new separate store.
* The "defaultForNew" predicate is also accepted, as a deprecated
* synonym for "default".
*/
function adoptACLDefault(doc, aclDoc, defaultResource, defaultACLDoc) {
var ACL = ns.acl;
var isContainer = doc.uri.slice(-1) === '/'; // Give default for all directories
var defaults = kb.each(undefined, ACL('default'), defaultResource, defaultACLDoc).concat(kb.each(undefined, ACL('defaultForNew'), defaultResource, defaultACLDoc));
var proposed = defaults.reduce(function (accumulatedStatements, da) {
return accumulatedStatements.concat(kb.statementsMatching(da, ns.rdf('type'), ACL('Authorization'), defaultACLDoc)).concat(kb.statementsMatching(da, ACL('agent'), undefined, defaultACLDoc)).concat(kb.statementsMatching(da, ACL('agentClass'), undefined, defaultACLDoc)).concat(kb.statementsMatching(da, ACL('agentGroup'), undefined, defaultACLDoc)).concat(kb.statementsMatching(da, ACL('origin'), undefined, defaultACLDoc)).concat(kb.statementsMatching(da, ACL('originClass'), undefined, defaultACLDoc)).concat(kb.statementsMatching(da, ACL('mode'), undefined, defaultACLDoc)).concat((0, _rdflib.st)(da, ACL('accessTo'), doc, defaultACLDoc)).concat(isContainer ? (0, _rdflib.st)(da, ACL('default'), doc, defaultACLDoc) : []);
}, []);
var kb2 = (0, _rdflib.graph)(); // Potential - derived is kept apart
proposed.forEach(function (st) {
return kb2.add(move(st.subject), move(st.predicate), move(st.object), (0, _rdflib.sym)(aclDoc.uri));
});
return kb2;
function move(symbol) {
var y = defaultACLDoc.uri.length; // The default ACL file
return (0, _rdflib.sym)(symbol.uri.slice(0, y) === defaultACLDoc.uri ? aclDoc.uri + symbol.uri.slice(y) : symbol.uri);
}
}
/**
* Read and canonicalize the ACL for x in aclDoc
*
* Accumulate the access rights which each agent or class has
*/
function readACL(doc, aclDoc) {
var kb2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : kb;
var getDefaults = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var auths = getDefaults ? getDefaultsFallback(kb2, ns) : kb2.each(undefined, ns.acl('accessTo'), doc);
var ACL = ns.acl;
var ac = {
agent: {},
agentClass: {},
agentGroup: {},
origin: {},
originClass: {}
};
Object.keys(ac).forEach(function (pred) {
auths.forEach(function (a) {
kb2.each(a, ACL('mode')).forEach(function (mode) {
kb2.each(a, ACL(pred)).forEach(function (agent) {
ac[pred][agent.uri] = ac[pred][agent.uri] || {};
ac[pred][agent.uri][mode.uri] = a; // could be "true" but leave pointer just in case
});
});
});
});
return ac;
function getDefaultsFallback(kb, ns) {
return kb.each(undefined, ns.acl('default'), doc).concat(kb.each(undefined, ns.acl('defaultForNew'), doc));
}
}
/**
* Compare two ACLs
*/
function sameACL(a, b) {
var contains = function contains(a, b) {
for (var pred in {
agent: true,
agentClass: true,
agentGroup: true,
origin: true,
originClass: true
}) {
if (a[pred]) {
for (var agent in a[pred]) {
for (var mode in a[pred][agent]) {
if (!b[pred][agent] || !b[pred][agent][mode]) {
return false;
}
}
}
}
}
return true;
};
return contains(a, b) && contains(b, a);
}
/**
* Union N ACLs
*/
function ACLunion(list) {
var b = list[0];
var a, ag;
var _loop = function _loop(k) {
;
['agent', 'agentClass', 'agentGroup', 'origin', 'originClass'].forEach(function (pred) {
a = list[k];
if (a[pred]) {
for (ag in a[pred]) {
for (var mode in a[pred][ag]) {
if (!b[pred][ag]) b[pred][ag] = [];
b[pred][ag][mode] = true;
}
}
}
});
};
for (var k = 1; k < list.length; k++) {
_loop(k);
}
return b;
}
/**
* Merge ACLs lists from things to form union
*/
function loadUnionACL(subjectList, callbackFunction) {
var aclList = [];
var _doList = function doList(list) {
if (list.length) {
var doc = list.shift().doc();
getACLorDefault(doc, function (ok, p2, targetDoc, targetACLDoc, defaultHolder, defaultACLDoc) {
var defa = !p2;
if (!ok || !defaultHolder || !defaultACLDoc) return callbackFunction(ok, targetACLDoc);
var acl = defa ? readACL(defaultHolder, defaultACLDoc) : readACL(targetDoc, targetACLDoc);
aclList.push(acl);
_doList(list.slice(1));
});
} else {
// all gone
callbackFunction(true, ACLunion(aclList));
}
};
_doList(subjectList);
}
/**
* Represents these as an RDF graph by combination of modes
*
* Each agent can only be in one place in this model, one combination of modes.
* Combos are like full control, read append, read only etc.
*/
function ACLbyCombination(ac) {
var byCombo = {};
['agent', 'agentClass', 'agentGroup', 'origin', 'originClass'].forEach(function (pred) {
for (var agent in ac[pred]) {
var combo = [];
for (var mode in ac[pred][agent]) {
combo.push(mode);
}
combo.sort();
var combo2 = combo.join('\n');
if (!byCombo[combo2]) byCombo[combo2] = [];
byCombo[combo2].push([pred, agent]);
}
});
return byCombo;
}
/**
* Write ACL graph to store from AC
*/
function makeACLGraph(kb, x, ac, aclDoc) {
var byCombo = ACLbyCombination(ac);
return makeACLGraphbyCombo(kb, x, byCombo, aclDoc);
}
/**
* Write ACL graph to store from combo
*/
function makeACLGraphbyCombo(kb, x, byCombo, aclDoc, main, defa) {
var ACL = ns.acl;
for (var combo in byCombo) {
var pairs = byCombo[combo];
if (!pairs.length) continue; // do not add to store when no agent
var modeURIs = combo.split('\n');
var _short = modeURIs.map(function (u) {
return u.split('#')[1];
}).join('');
if (defa && !main) _short += 'Default'; // don't muddle authorizations
var a = kb.sym(aclDoc.uri + '#' + _short);
kb.add(a, ns.rdf('type'), ACL('Authorization'), aclDoc);
if (main) {
kb.add(a, ACL('accessTo'), x, aclDoc);
}
if (defa) {
kb.add(a, ACL('default'), x, aclDoc);
}
for (var i = 0; i < modeURIs.length; i++) {
kb.add(a, ACL('mode'), kb.sym(modeURIs[i]), aclDoc);
}
for (var _i = 0; _i < pairs.length; _i++) {
var pred = pairs[_i][0];
var ag = pairs[_i][1];
kb.add(a, ACL(pred), kb.sym(ag), aclDoc);
}
}
}
/**
* Debugging short strings for dumping ACL
* and possibly in the UI
*/
function ACLToString(ac) {
return comboToString(ACLbyCombination(ac));
}
/**
* Convert a [[ComboList]] to a string
*/
function comboToString(byCombo) {
var str = '';
for (var combo in byCombo) {
var modeURIs = combo.split('\n');
var initials = modeURIs.map(function (u) {
return u.split('#')[1][0];
}).join('');
str += initials + ':';
var pairs = byCombo[combo];
for (var i = 0; i < pairs.length; i++) {
var pred = pairs[i][0];
var ag = (0, _rdflib.sym)(pairs[i][1]);
str += pred === 'agent' ? '@' : '';
str += ag.sameTerm(ns.foaf('Agent')) ? '*' : utils.label(ag);
if (i < pairs.length - 1) str += ',';
}
str += ';';
}
return '{' + str.slice(0, -1) + '}'; // drop extra semicolon
}
/**
* Write ACL graph as Turtle
*/
function makeACLString(x, ac, aclDoc) {
var kb2 = (0, _rdflib.graph)();
makeACLGraph(kb2, x, ac, aclDoc);
return (0, _rdflib.serialize)(aclDoc, kb2, aclDoc.uri, 'text/turtle') || '';
}
/**
* Write ACL graph to web
*/
function putACLObject(kb, x, ac, aclDoc, callbackFunction) {
var byCombo = ACLbyCombination(ac);
return putACLbyCombo(kb, x, byCombo, aclDoc, callbackFunction);
}
/**
* Write ACL graph to web from a [[ComboList]]
*/
function putACLbyCombo(kb, x, byCombo, aclDoc, callbackFunction) {
var _kb$updater;
var kb2 = (0, _rdflib.graph)();
makeACLGraphbyCombo(kb2, x, byCombo, aclDoc, true);
// const str = makeACLString = function(x, ac, aclDoc)
(_kb$updater = kb.updater) === null || _kb$updater === void 0 || _kb$updater.put(aclDoc, kb2.statementsMatching(undefined, undefined, undefined, aclDoc), 'text/turtle', function (uri, ok, message) {
if (!ok) {
callbackFunction(ok, message);
} else {
var _kb$fetcher;
(_kb$fetcher = kb.fetcher) === null || _kb$fetcher === void 0 || _kb$fetcher.unload(aclDoc);
makeACLGraphbyCombo(kb, x, byCombo, aclDoc, true);
kb.fetcher.requested[aclDoc.uri] = 'done'; // missing: save headers
callbackFunction(ok);
}
});
}
/**
* Fix the ACl for an individual card as a function of the groups it is in
*
* All group files must be loaded first
*/
function fixIndividualCardACL(person, log, callbackFunction) {
var groups = kb.each(undefined, ns.vcard('hasMember'), person);
// const doc = person.doc()
if (groups) {
fixIndividualACL(person, groups, log, callbackFunction);
} else {
log('This card is in no groups');
callbackFunction(true); // fine, no requirements to access. default should be ok
}
// @@ if no groups, then use default for People container or the book top container.?
}
/**
* This function is used by [[fixIndividualCardACL]]
*/
function fixIndividualACL(item, subjects, log, callbackFunction) {
log = log || debug.log;
var doc = item.doc();
getACLorDefault(doc, function (ok, exists, targetDoc, targetACLDoc, defaultHolder, defaultACLDoc) {
if (!ok || !defaultHolder || !defaultACLDoc) return callbackFunction(false, targetACLDoc); // ie message
var ac = exists ? readACL(targetDoc, targetACLDoc) : readACL(defaultHolder, defaultACLDoc);
loadUnionACL(subjects, function (ok, union) {
if (!ok) return callbackFunction(false, union);
if (sameACL(union, ac)) {
log('Nice - same ACL. no change ' + utils.label(item) + ' ' + doc);
} else {
log('Group ACLs differ for ' + utils.label(item) + ' ' + doc);
// log("Group ACLs: " + makeACLString(targetDoc, union, targetACLDoc))
// log((exists ? "Previous set" : "Default") + " ACLs: " +
// makeACLString(targetDoc, ac, targetACLDoc))
putACLObject(kb, targetDoc, union, targetACLDoc, callbackFunction);
}
});
});
}
/**
* Set an ACL
*/
function setACL(docURI, aclText, callbackFunction) {
var aclDoc = kb.any(docURI, _solidLogic.ACL_LINK); // @@ check that this get set by web.js
if (!kb.fetcher) {
throw new Error('Store has no fetcher');
}
if (aclDoc) {
// Great we already know where it is
kb.fetcher.webOperation('PUT', aclDoc.value, {
data: aclText,
contentType: 'text/turtle'
}).then(function (res) {
callbackFunction(res.ok, res.error || '');
}); // @@@ check params
} else {
kb.fetcher.nowOrWhenFetched(docURI, undefined, function (ok, body) {
if (!ok) return callbackFunction(ok, 'Gettting headers for ACL: ' + body);
var aclDoc = kb.any(docURI, _solidLogic.ACL_LINK); // @@ check that this get set by web.js
if (!aclDoc) {
// complainIfBad(false, "No Link rel=ACL header for " + docURI)
callbackFunction(false, 'No Link rel=ACL header for ' + docURI);
} else {
if (!kb.fetcher) {
throw new Error('Store has no fetcher');
}
kb.fetcher.webOperation('PUT', aclDoc.value, {
data: aclText,
contentType: 'text/turtle'
}).then(function (res) {
callbackFunction(res.ok, res.error || '');
});
}
});
}
}
/**
* Get ACL file or default if necessary
*
* @param callbackFunction Will be called in the following ways, in the following cases:
* * `callbackFunction(true, true, doc, aclDoc)` if the ACL did exist
* * `callbackFunction(true, false, doc, aclDoc, defaultHolder, defaultACLDoc)` if the ACL file did not exist but a default did
* * `callbackFunction(false, false, status, message)` when there was an error getting the original
* * `callbackFunction(false, true, status, message)` when there was an error getting the default
*/
function getACLorDefault(doc, callbackFunction) {
getACL(doc, function (ok, status, aclDoc, message) {
var ACL = ns.acl;
if (!ok) return callbackFunction(false, false, status, message);
// Recursively search for the ACL file which gives default access
var _tryParent = function tryParent(uri) {
if (uri.slice(-1) === '/') {
uri = uri.slice(0, -1);
}
var right = uri.lastIndexOf('/');
var left = uri.indexOf('/', uri.indexOf('//') + 2);
if (left > right) {
return callbackFunction(false, true, 404, 'Found no ACL resource');
}
uri = uri.slice(0, right + 1);
var doc2 = (0, _rdflib.sym)(uri);
getACL(doc2, function (ok, status, defaultACLDoc) {
if (!ok) {
return callbackFunction(false, true, status, "( No ACL pointer ".concat(uri, " ").concat(status, ")").concat(defaultACLDoc));
} else if (status === 403) {
return callbackFunction(false, true, status, "( default ACL file FORBIDDEN. Stop.".concat(uri, ")"));
} else if (status === 404) {
return _tryParent(uri);
} else if (status !== 200) {
return callbackFunction(false, true, status, "Error status '".concat(status, "' searching for default for ").concat(doc2));
}
// 200
// statusBlock.textContent += (" ACCESS set at " + uri + ". End search.")
var defaults = kb.each(undefined, ACL('default'), kb.sym(uri), defaultACLDoc).concat(kb.each(undefined, ACL('defaultForNew'), kb.sym(uri), defaultACLDoc));
if (!defaults.length) {
return _tryParent(uri); // Keep searching
}
var defaultHolder = kb.sym(uri);
return callbackFunction(true, false, doc, aclDoc, defaultHolder, defaultACLDoc);
});
}; // tryParent
if (!ok) {
return callbackFunction(false, false, status, "Error accessing Access Control information for ".concat(doc, ") ").concat(message));
} else if (status === 404) {
_tryParent(doc.uri); // @@ construct default one - the server should do that
} else if (status === 403) {
return callbackFunction(false, false, status, "(Sharing not available to you)".concat(message));
} else if (status !== 200) {
return callbackFunction(false, false, status, "Error ".concat(status, " accessing Access Control information for ").concat(doc, ": ").concat(message));
} else {
// 200
return callbackFunction(true, true, doc, aclDoc);
}
}); // Call to getACL
}
/**
* Calls back `(ok, status, acldoc, message)` as follows
*
* * `(false, 900, errormessage)` if no link header
* * `(true, 403, documentSymbol, fileaccesserror)` if not authorized
* * `(true, 404, documentSymbol, fileaccesserror)` if does not exist
* * `(true, 200, documentSymbol)` if file exists and read OK
*/
function getACL(doc, callbackFunction) {
if (!kb.fetcher) {
throw new Error('kb has no fetcher');
}
kb.fetcher.nowOrWhenFetched(doc, undefined, function (ok, body) {
if (!ok) {
return callbackFunction(ok, "Can't get headers to find ACL for ".concat(doc, ": ").concat(body));
}
var aclDoc = kb.any(doc, _solidLogic.ACL_LINK); // @@ check that this get set by web.js
if (!aclDoc) {
callbackFunction(false, 900, "No Link rel=ACL header for ".concat(doc));
} else {
if (!kb.fetcher) {
throw new Error('kb has no fetcher');
}
if (kb.fetcher.nonexistent[aclDoc.value]) {
return callbackFunction(true, 404, aclDoc, "ACL file ".concat(aclDoc, " does not exist."));
}
kb.fetcher.nowOrWhenFetched(aclDoc, undefined, function (ok, message, response) {
if (!ok) {
callbackFunction(true, response.status, aclDoc, "Can't read Access Control File ".concat(aclDoc, ": ").concat(message));
} else {
callbackFunction(true, 200, aclDoc);
}
});
}
});
}
/**
* Calls [[getACLorDefault]] and then (?)
*/
function getProspectiveHolder(_x) {
return _getProspectiveHolder.apply(this, arguments);
}
function _getProspectiveHolder() {
_getProspectiveHolder = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(targetDirectory) {
return _regenerator["default"].wrap(function (_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
return _context.abrupt("return", new Promise(function (resolve, reject) {
return getACLorDefault((0, _rdflib.sym)(targetDirectory), function (ok, isDirectACL, targetDoc, targetACLDoc, defaultHolder) {
if (ok) {
return resolve(isDirectACL ? targetDoc : defaultHolder);
}
return reject(new Error("Error loading ".concat(targetDirectory)));
});
}));
case 1:
case "end":
return _context.stop();
}
}, _callee);
}));
return _getProspectiveHolder.apply(this, arguments);
}
//# sourceMappingURL=acl.js.map