diff --git a/.travis.yml b/.travis.yml index f3ddcbba7..b91470265 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,8 @@ addons: - nicola.localhost before_install: - - echo "No GitHub dependencies allowed" && - ! grep '"github:' package-lock.json + #- echo "No GitHub dependencies allowed" && + # ! grep '"github:' package-lock.json - npm install -g npm@latest install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 541ca77fb..d250c0fc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,12 +21,17 @@ - The browser-reported `Origin` header will now be checked by default, and the ACL system can be used to restrict access to applications for added security. + - Users can add `trustedApp` entries to their profile using a new databrowser pane. + You will see an 'A' icon added while you view a Person's profile URL + with the data browser (might have to hit refresh in your browser and make sure you + are viewing a WebId URL like https://localhost:8443/profile/card#me). - Logging is now verbose by default so the `-v` option has been removed and a `--quiet` option has been added to mute the log. - To be bug compliant with 4.x releases, if a rule for public readable root / does not exist, it will check in /index.html.acl (see issue #1063) - Command line options are now kebab-cased rather than camelCased, config options may be both. +- Resource with no extension now have '$.ttl' appended in the filename (see upgrades notes below). - Many smaller fixes. #### 5.0.0 Upgrade Notes diff --git a/hard_link/acl-check.js b/hard_link/acl-check.js new file mode 100644 index 000000000..51f3a09f2 --- /dev/null +++ b/hard_link/acl-check.js @@ -0,0 +1,217 @@ +// Access control logic + +const $rdf = require('rdflib') + +const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#') +const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/') +const VCARD = $rdf.Namespace('http://www.w3.org/2006/vcard/ns#') + +let _logger + +function publisherTrustedApp (kb, doc, aclDoc, modesRequired, origin, docAuths) { + let app = $rdf.sym(origin) + let appAuths = docAuths.filter(auth => kb.holds(auth, ACL('mode'), ACL('Control'), aclDoc)) + let owners = appAuths.map(auth => kb.each(auth, ACL('agent'))).flat() // owners + let relevant = owners.map(owner => kb.each(owner, ACL('trust'), null, owner.doc()).filter( + ta => kb.holds(ta, ACL('trustedApp'), app, owner.doc()))).flat() // ta's + let modesOK = relevant.map(ta => kb.each(ta, ACL('mode'))).flat().map(m => m.uri) + let modesRequiredURIs = modesRequired.map(m => m.uri) + modesRequiredURIs.every(uri => modesOK.includes(uri)) + // modesRequired.every(mode => appAuths.some(auth => kb.holds(auth, ACL('mode'), mode, aclDoc))) +} + +function accessDenied (kb, doc, directory, aclDoc, agent, modesRequired, origin, trustedOrigins, originTrustedModes = []) { + log(`accessDenied: checking access to ${doc} by ${agent} and origin ${origin}`) + const modeURIorReasons = modesAllowed(kb, doc, directory, aclDoc, agent, origin, trustedOrigins, originTrustedModes) + let ok = false + log('accessDenied: modeURIorReasons: ' + JSON.stringify(Array.from(modeURIorReasons))) + modesRequired.forEach(mode => { + log(` checking ` + mode) + if (modeURIorReasons.has(mode.uri)) { + log(' Mode required and allowed:' + mode) + } else if (mode.sameTerm(ACL('Append')) && modeURIorReasons.has(ACL('Write').uri)) { + log(' Append required and Write allowed. OK') + } else { + ok = modeURIorReasons.values().next().value || 'Forbidden' + if (ok.startsWith('http')) { + // Then, the situation is that one mode has failed, the other + // has passed, and we get URI of the one that passed, but that's not a good error + ok = 'All Required Access Modes Not Granted' + } + log(' MODE REQUIRED NOT ALLOWED: ' + mode + ' Denying with ' + ok) + } + }) + return ok +} + +async function getTrustedModesForOrigin (kb, doc, directory, aclDoc, origin, fetch) { + // FIXME: this is duplicate code from the modesAllowed function, will refactor, + // see https://github.com/solid/acl-check/issues/22 + var auths + if (!directory) { // Normal case, ACL for a file + auths = kb.each(null, ACL('accessTo'), doc, aclDoc) + log(` ${auths.length} direct authentications about ${doc}`) + } else { + auths = kb.each(null, ACL('default'), directory, null) + auths = auths.concat(kb.each(null, ACL('defaultForNew'), directory, null)) // Deprecated but keep for ages + log(` ${auths.length} default authentications about ${directory} in ${aclDoc}`) + } + const ownerAuths = auths.filter(auth => kb.holds(auth, ACL('mode'), ACL('Control'), aclDoc)) + const owners = ownerAuths.reduce((acc, auth) => acc.concat(kb.each(auth, ACL('agent'))), []) // owners + let result + try { + result = await Promise.all(owners.map(owner => { + return fetch(owner).then(() => { + const q = ` + SELECT ?mode WHERE { + ${owner} ${ACL('trustedApp')} ?trustedOrigin. + ?trustedOrigin ${ACL('origin')} ${origin}; + ${ACL('mode')} ?mode . + }` + return query(q, kb) + }).catch(e => { + log('could not fetch owner doc', owner, e.message) + }) + })) + } catch (e) { + log('error checking owner profiles', e.message) + } + let trustedModes = [] + result.map(ownerResults => ownerResults.map(entry => { + trustedModes.push(entry['?mode']) + })) + return Promise.resolve(trustedModes) +} + +async function query (queryString, store) { + return new Promise((resolve, reject) => { + try { + const query = $rdf.SPARQLToQuery(queryString, true, store) + const results = [] + store.query(query, (result) => { + results.push(result) + }, null, () => { + resolve(results) + }) + } catch (err) { + reject(err) + } + }) +} + +/* Function checkAccess +** @param kb A quadstore +** @param doc the resource (A named node) or directory for which ACL applies +*/ +function checkAccess (kb, doc, directory, aclDoc, agent, modesRequired, origin, trustedOrigins, originTrustedModes) { + return !accessDenied(kb, doc, directory, aclDoc, agent, modesRequired, origin, trustedOrigins, originTrustedModes) +} + +function modesAllowed (kb, doc, directory, aclDoc, agent, origin, trustedOrigins, originTrustedModes = []) { + var auths + if (!directory) { // Normal case, ACL for a file + auths = kb.each(null, ACL('accessTo'), doc, aclDoc) + log(` ${auths.length} direct authentications about ${doc}`) + } else { + auths = kb.each(null, ACL('default'), directory, null) + auths = auths.concat(kb.each(null, ACL('defaultForNew'), directory, null)) // Deprecated but keep for ages + log(` ${auths.length} default authentications about ${directory} in ${aclDoc}`) + } + if (origin && trustedOrigins && nodesIncludeNode(trustedOrigins, origin)) { + log('Origin ' + origin + ' is trusted') + origin = null // stop worrying about origin + log(` modesAllowed: Origin ${origin} is trusted.`) + } + + function agentOrGroupOK (auth, agent) { + log(` Checking auth ${auth} with agent ${agent}`) + if (!agent) { + log(` Agent or group: Fail: not public and not logged on.`) + return false + } + if (kb.holds(auth, ACL('agentClass'), ACL('AuthenticatedAgent'), aclDoc)) { + log(' AuthenticatedAgent: logged in, looks good') + return true + } + if (kb.holds(auth, ACL('agent'), agent, aclDoc)) { + log(' Agent explicitly authenticated.') + return true + } + if (kb.each(auth, ACL('agentGroup'), null, aclDoc).some( + group => kb.holds(group, VCARD('hasMember'), agent, group.doc()))) { + log(' Agent is member of group which has access.') + return true + } + log(' Agent or group access fails for this authentication.') + return false + } // Agent or group + + function originOK (auth, origin) { + return kb.holds(auth, ACL('origin'), origin, aclDoc) + } + + function agentAndAppFail (auth) { + if (kb.holds(auth, ACL('agentClass'), FOAF('Agent'), aclDoc)) { + log(` Agent or group: Ok, its public.`) + return false + } + if (!agentOrGroupOK(auth, agent)) { + log(' The agent/group check fails') + return 'User Unauthorized' + } + if (!origin) { + log(' Origin check not needed: no origin.') + return false + } + if (originTrustedModes && originTrustedModes.length > 0) { + log(` Origin might have access (${originTrustedModes.join(', ')})`) + return false + } + if (originOK(auth, origin)) { + log(' Origin check succeeded.') + return false + } + log(' Origin check FAILED. Origin not trusted.') + return 'Origin Unauthorized' // @@ look for other trusted apps + } + + var modeURIorReasons = new Set() + + auths.forEach(auth => { + let agentAndAppStatus = agentAndAppFail(auth) + if (agentAndAppStatus) { + log(' Check failed: ' + agentAndAppStatus) + modeURIorReasons.add(agentAndAppStatus) + } else { + let modes = kb.each(auth, ACL('mode'), null, aclDoc) + if (originTrustedModes && originTrustedModes.length > 0) { + modes = modes.filter(mode => nodesIncludeNode(originTrustedModes, mode)) + } + modes.forEach(mode => { + log(' Mode allowed: ' + mode) + modeURIorReasons.add(mode.uri) + }) + } + }) + return modeURIorReasons +} + +function nodesIncludeNode (nodes, node) { + return nodes.some(trustedOrigin => trustedOrigin.termType === node.termType && trustedOrigin.value === node.value) +} + +function configureLogger (logger) { + _logger = logger +} + +function log (...msgs) { + return (_logger || console.log).apply(_logger, msgs) +} + +module.exports.accessDenied = accessDenied +module.exports.checkAccess = checkAccess +module.exports.configureLogger = configureLogger +module.exports.getTrustedModesForOrigin = getTrustedModesForOrigin +module.exports.log = log +module.exports.modesAllowed = modesAllowed +module.exports.publisherTrustedApp = publisherTrustedApp diff --git a/lib/acl-checker.js b/lib/acl-checker.js index 44c0ae883..ffca8423e 100644 --- a/lib/acl-checker.js +++ b/lib/acl-checker.js @@ -3,7 +3,7 @@ const rdf = require('rdflib') const debug = require('./debug').ACL const HTTPError = require('./http-error') -const aclCheck = require('@solid/acl-check') +const aclCheck = require('../hard_link/acl-check') const { URL } = require('url') const { promisify } = require('util') const fs = require('fs') @@ -57,8 +57,16 @@ class ACLChecker { const modes = [ACL(mode)] const agentOrigin = this.agentOrigin const trustedOrigins = this.trustedOrigins - const accessDenied = aclCheck.accessDenied(acl.graph, resource, directory, aclFile, agent, modes, agentOrigin, trustedOrigins) - + let originTrustedModes = [] + try { + this.fetch(aclFile.doc().value) + originTrustedModes = await aclCheck.getTrustedModesForOrigin(acl.graph, resource, directory, aclFile, agentOrigin, (uriNode) => { + return this.fetch(uriNode.doc().value, acl.graph) + }) + } catch (e) { + console.error(e.message) + } + const accessDenied = aclCheck.accessDenied(acl.graph, resource, directory, aclFile, agent, modes, agentOrigin, trustedOrigins, originTrustedModes) if (accessDenied && user) { this.messagesCached[cacheKey].push(HTTPError(403, accessDenied)) } else if (accessDenied) { diff --git a/lib/create-app.js b/lib/create-app.js index 88c2c251e..ff7aabad6 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -24,7 +24,7 @@ const debug = require('./debug') const path = require('path') const { routeResolvedFile } = require('./utils') const ResourceMapper = require('./resource-mapper') -const aclCheck = require('@solid/acl-check') +const aclCheck = require('../hard_link/acl-check') const { version } = require('../package.json') const corsSettings = cors({ diff --git a/package-lock.json b/package-lock.json index 433505a8c..543220db3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -219,29 +219,6 @@ "integrity": "sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA==", "dev": true }, - "@solid/acl-check": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@solid/acl-check/-/acl-check-0.1.3.tgz", - "integrity": "sha512-Rvdxn7SOlqo1BtiY6dcFlJAjne0QK1hrsFL1q3HUDg7yleOEXBm1ESyZ0XDM6jwqJHqKf+0l987D15k5MPyIng==", - "requires": { - "rdflib": "^0.12.1", - "solid-namespace": "0.1.0" - }, - "dependencies": { - "rdflib": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/rdflib/-/rdflib-0.12.3.tgz", - "integrity": "sha1-K0IGamYJwGtAqQSFhJNix8uV6h4=", - "requires": { - "async": "^0.9.x", - "jsonld": "^0.4.5", - "n3": "^0.4.1", - "xmldom": "^0.1.22", - "xmlhttprequest": "^1.7.0" - } - } - } - }, "@solid/better-simple-slideshow": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@solid/better-simple-slideshow/-/better-simple-slideshow-0.1.0.tgz", @@ -1944,9 +1921,9 @@ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" }, "caniuse-lite": { - "version": "1.0.30000911", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000911.tgz", - "integrity": "sha512-x/E/SNwD80I0bT+fF9Y3Kbwo7Xd1xSafCAmFlpJmaVg3SQoJJOH4Ivb9fi9S0WjfqewQ6Ydt1zEVZpmMVYNeDA==" + "version": "1.0.30000946", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000946.tgz", + "integrity": "sha512-ZVXtMoZ3Mfq69Ikv587Av+5lwGVJsG98QKUucVmtFBf0tl1kOCfLQ5o6Z2zBNis4Mx3iuH77WxEUpdP6t7f2CQ==" }, "capture-stack-trace": { "version": "1.0.1", @@ -1998,16 +1975,16 @@ "dev": true }, "chat-pane": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/chat-pane/-/chat-pane-1.2.6.tgz", - "integrity": "sha512-+zPpE2zVgg4wNhb2+NxmqhEu+OXmlAEPKoHmoKXRr8oUrQOlSFWkr8jtZKuQwukziC4FltvY2u0rYRfro4KDyQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/chat-pane/-/chat-pane-1.2.7.tgz", + "integrity": "sha512-itsMQWUNjajgdEiTNaRMl/7y9FEpCNrPNldfu+Dj+iJPE5Y0OIBOwzx6hoyItiJYsQSfMcsFiGss2Ie5U4YZtQ==", "requires": { "babel-preset-env": "^1.6.1", "babel-preset-metalab": "^1.0.0", "mime-types": "^2.1.13", "pane-registry": ">1.0.0", "rdflib": ">=0.17.0", - "solid-ui": ">=0.11.3" + "solid-ui": ">=0.12.0" } }, "check-error": { @@ -2336,9 +2313,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" }, "core-util-is": { "version": "1.0.2", @@ -2868,9 +2845,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.84.tgz", - "integrity": "sha512-IYhbzJYOopiTaNWMBp7RjbecUBsbnbDneOP86f3qvS0G0xfzwNSvMJpTrvi5/Y1gU7tg2NAgeg8a8rCYvW9Whw==" + "version": "1.3.115", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.115.tgz", + "integrity": "sha512-mN2qeapQWdi2B9uddxTZ4nl80y46hbyKY5Wt9Yjih+QZFQLdaujEDK4qJky35WhyxMzHF3ZY41Lgjd2BPDuBhg==" }, "elliptic": { "version": "6.4.1", @@ -3564,9 +3541,9 @@ } }, "find-babel-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.1.0.tgz", - "integrity": "sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz", + "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", "requires": { "json5": "^0.5.1", "path-exists": "^3.0.0" @@ -4642,14 +4619,14 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "issue-pane": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/issue-pane/-/issue-pane-1.0.3.tgz", - "integrity": "sha512-K/r9weoQexIFiG9vvYygzHF5IPNbKZluBZ1hERDU2sTlLsAnRKGJ9ms0Xb/Yn+MF9tYt82rno3E6OE3+satpPQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/issue-pane/-/issue-pane-1.0.4.tgz", + "integrity": "sha512-wxDXpwmq4DQ2Fb6aRsNpFhaWTtcfnx6n+puOM5fHih1Im8gHy8C+qvufNgqfq1SlipFzskXOc8Cre61tj4xEuA==", "requires": { "babel-preset-env": "^1.6.1", "babel-preset-metalab": "^1.0.0", "mime-types": "^2.1.13", - "pane-registry": ">=1.0.0", + "pane-registry": "^1.0.4", "rdflib": ">=0.17.0", "solid-ui": ">=0.11.3" } @@ -4733,7 +4710,7 @@ }, "json5": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" }, "jsonfile": { @@ -5047,12 +5024,12 @@ } }, "mashlib": { - "version": "0.7.17", - "resolved": "https://registry.npmjs.org/mashlib/-/mashlib-0.7.17.tgz", - "integrity": "sha512-asVogWGHOSaFkEb1YZZL+qOGHv26m8IaYBy83E5HYbXw0DaUA11FXxK3+hC+d1ite3y+d66JLvOZ5CyrXELQwg==", + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/mashlib/-/mashlib-0.7.18.tgz", + "integrity": "sha512-PeZSqIX31WRq4WBmhUylguSHK4pyHmRduhvasumbO2FW5gvsG5QvypRQKkc4BBQtSBgh9ozE8C0ktOYU0fVpog==", "requires": { "rdflib": ">=0.19.1", - "solid-panes": ">=1.1.19" + "solid-panes": ">=1.3.1" }, "dependencies": { "rdflib": { @@ -5066,6 +5043,26 @@ "solid-auth-client": "^2.2.3", "xmldom": "^0.1.22" } + }, + "solid-panes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/solid-panes/-/solid-panes-1.3.1.tgz", + "integrity": "sha512-SGw98zxvsp9gNzi1pv7jPWC4+O3e4xySPePFipBA7WN6BlCPweOlg+7Bm6xp7kj0jFSydu3+ckdM78W/xxuKKQ==", + "requires": { + "@solid/better-simple-slideshow": "^0.1.0", + "babel-preset-env": "^1.6.1", + "babel-preset-metalab": "^1.0.0", + "chat-pane": ">=1.2.7", + "contacts-pane": "^1.0.3", + "folder-pane": "^1.0.4", + "issue-pane": "^1.0.4", + "meeting-pane": "^1.0.4", + "mime-types": "^2.1.13", + "pane-registry": "^1.0.4", + "rdflib": ">=0.17.1", + "solid-ui": ">=0.12.0", + "source-pane": "^1.0.3" + } } } }, @@ -5085,9 +5082,9 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "meeting-pane": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/meeting-pane/-/meeting-pane-1.0.2.tgz", - "integrity": "sha512-TKroX1nW9RTHKH0pULY38vFV0HL0Bgy600bPT6mori6YINCfyw09LgkbinJX9PYYVw6t6x0ZEwWAP4gc60p/kg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/meeting-pane/-/meeting-pane-1.0.4.tgz", + "integrity": "sha512-mOaWBr1p0tUfzlyDYSeZOV7M6B3kegohhqAy/auTcgi/8papnSxtME5lRyqrvmlJoXO5vsPFPDnvbeevWWfmsg==", "requires": { "babel-preset-env": "^1.6.1", "babel-preset-metalab": "^1.0.0", @@ -8587,37 +8584,18 @@ "rdf-ns": "^0.1.0" } }, - "solid-panes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/solid-panes/-/solid-panes-1.2.0.tgz", - "integrity": "sha512-JjdYi0KjVYCA5aOqs4UA/aU9ZISkdO4TWpGv7btfDQQGc+cBoXdSRwlufqFGHz3SrzBnqc0JJvmHsCvEQ4FREw==", - "requires": { - "@solid/better-simple-slideshow": "^0.1.0", - "babel-preset-env": "^1.6.1", - "babel-preset-metalab": "^1.0.0", - "chat-pane": "^1.2.6", - "contacts-pane": "^1.0.3", - "folder-pane": "^1.0.4", - "issue-pane": "^1.0.3", - "meeting-pane": "^1.0.2", - "mime-types": "^2.1.13", - "pane-registry": "^1.0.4", - "rdflib": ">=0.17.1", - "solid-ui": ">=0.11.5", - "source-pane": "^1.0.3" - } - }, "solid-ui": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-0.11.12.tgz", - "integrity": "sha512-Cw2EEbooJnOlCuny3VmPVaPZ6byaQq3EA67B/j2mUhNJvQOtT1bHtqey6veeF9e8LMzBH3ndVeBCdf2awPfD/A==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-0.12.0.tgz", + "integrity": "sha512-ImiLiBE0SZP61kZ5dWweDRKQPxHYTFI8Jmxsz4yQIyPVcQB9SGM0Tdchz4zmSDRPvuw8GRFsC3USIHwIuvxj2g==", "requires": { "escape-html": "^1.0.3", + "mime-types": "^2.1.20", "node-uuid": "^1.4.7", - "rdflib": ">=0.19.0", - "regenerate": "^1.3.2", - "solid-auth-client": "^2.2.6", - "solid-auth-tls": "^0.1.2" + "rdflib": "^0.19.1", + "solid-auth-client": "^2.2.13", + "solid-auth-tls": "^0.1.2", + "solid-namespace": "0.2.0" }, "dependencies": { "node-uuid": { @@ -8636,6 +8614,11 @@ "solid-auth-client": "^2.2.3", "xmldom": "^0.1.22" } + }, + "solid-namespace": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/solid-namespace/-/solid-namespace-0.2.0.tgz", + "integrity": "sha512-yQGQlTNDVtcMfzLz7OwL6Z8lJy9rN1ep0MgX28DPcja0DtA5pu1MzTpBVD9kvXl9X6eSEX73I7IYt0cUim0DrA==" } } }, diff --git a/package.json b/package.json index 80f18a868..f2b6d0802 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "bugs": "https://github.com/solid/node-solid-server/issues", "dependencies": { "@solid/oidc-auth-manager": "^0.17.1", - "@solid/acl-check": "^0.1.3", "body-parser": "^1.18.3", "bootstrap": "^3.3.7", "busboy": "^0.2.12", @@ -82,7 +81,7 @@ "ip-range-check": "0.0.2", "is-ip": "^2.0.0", "li": "^1.0.1", - "mashlib": "^0.7.15", + "mashlib": "^0.7.18", "mime-types": "^2.1.11", "negotiator": "^0.6.0", "node-fetch": "^2.1.2", diff --git a/test/integration/authentication-oidc-test.js b/test/integration/authentication-oidc-test.js index 53827f5f7..e2153a946 100644 --- a/test/integration/authentication-oidc-test.js +++ b/test/integration/authentication-oidc-test.js @@ -20,7 +20,7 @@ chai.use(require('dirty-chai')) // In this test we always assume that we are Alice describe('Authentication API (OIDC)', () => { - let alice, bob + let alice, bob // eslint-disable-line no-unused-vars let aliceServerUri = 'https://localhost:7000' let aliceWebId = 'https://localhost:7000/profile/card#me' @@ -35,6 +35,8 @@ describe('Authentication API (OIDC)', () => { let bobDbPath = path.join(__dirname, '../resources/accounts-scenario/bob/db') + const trustedAppUri = 'https://trusted.app' + const serverConfig = { sslKey: path.join(__dirname, '../keys/key.pem'), sslCert: path.join(__dirname, '../keys/cert.pem'), @@ -437,6 +439,48 @@ describe('Authentication API (OIDC)', () => { expect(response).to.have.property('status', 401) }) }) + + describe('with trusted app and no cookie', () => { + before(done => { + alice.get('/private-for-alice.txt') + .set('Origin', trustedAppUri) + .end((err, res) => { + response = res + done(err) + }) + }) + + it('should return a 401', () => expect(response).to.have.property('status', 401)) + }) + + describe('with trusted app and malicious cookie', () => { + before(done => { + var malcookie = cookie.replace(/connect\.sid=(\S+)/, 'connect.sid=l33th4x0rzp0wn4g3;') + alice.get('/private-for-alice.txt') + .set('Cookie', malcookie) + .set('Origin', trustedAppUri) + .end((err, res) => { + response = res + done(err) + }) + }) + + it('should return a 401', () => expect(response).to.have.property('status', 401)) + }) + + describe('with trusted app and correct cookie', () => { + before(done => { + alice.get('/private-for-alice.txt') + .set('Cookie', cookie) + .set('Origin', trustedAppUri) + .end((err, res) => { + response = res + done(err) + }) + }) + + it('should return a 200', () => expect(response).to.have.property('status', 200)) + }) }) }) diff --git a/test/integration/authentication-oidc-with-strict-origins-turned-off-test.js b/test/integration/authentication-oidc-with-strict-origins-turned-off-test.js index f61ca8ab6..7cf97db3a 100644 --- a/test/integration/authentication-oidc-with-strict-origins-turned-off-test.js +++ b/test/integration/authentication-oidc-with-strict-origins-turned-off-test.js @@ -35,6 +35,8 @@ describe('Authentication API (OIDC) - With strict origins turned off', () => { const bobServerUri = `https://localhost:${bobServerPort}` let bobDbPath = path.join(__dirname, '../resources/accounts-strict-origin-off/bob/db') + const trustedAppUri = 'https://trusted.app' + const serverConfig = { sslKey: path.join(__dirname, '../keys/key.pem'), sslCert: path.join(__dirname, '../keys/cert.pem'), @@ -194,6 +196,18 @@ describe('Authentication API (OIDC) - With strict origins turned off', () => { }) }) + it('should return a 401', () => expect(response).to.have.property('status', 401)) + }) + describe('and trusted app', () => { + before(done => { + alice.get('/private-for-alice.txt') + .set('Origin', trustedAppUri) + .end((err, res) => { + response = res + done(err) + }) + }) + it('should return a 401', () => expect(response).to.have.property('status', 401)) }) }) @@ -251,6 +265,21 @@ describe('Authentication API (OIDC) - With strict origins turned off', () => { // Even if origin checking is disabled, then this should return a 401 because cookies should not be trusted cross-origin it('should return a 401', () => expect(response).to.have.property('status', 401)) }) + + describe('and trusted app', () => { + // Trusted apps are not supported when strictOrigin check is turned off + before(done => { + alice.get('/private-for-alice.txt') + .set('Cookie', cookie) + .set('Origin', trustedAppUri) + .end((err, res) => { + response = res + done(err) + }) + }) + + it('should return a 401', () => expect(response).to.have.property('status', 401)) + }) }) describe('with malicious cookie', () => { @@ -310,6 +339,20 @@ describe('Authentication API (OIDC) - With strict origins turned off', () => { it('should return a 401', () => expect(response).to.have.property('status', 401)) }) + + describe('and trusted app', () => { + before(done => { + alice.get('/private-for-alice.txt') + .set('Cookie', malcookie) + .set('Origin', trustedAppUri) + .end((err, res) => { + response = res + done(err) + }) + }) + + it('should return a 401', () => expect(response).to.have.property('status', 401)) + }) }) }) }) diff --git a/test/resources/accounts-scenario/alice/profile/card$.ttl b/test/resources/accounts-scenario/alice/profile/card$.ttl new file mode 100644 index 000000000..a014d960e --- /dev/null +++ b/test/resources/accounts-scenario/alice/profile/card$.ttl @@ -0,0 +1,4 @@ +@prefix acl: . + +<#me> acl:trustedApp [ acl:origin ; + acl:mode acl:Read, acl:Write, acl:Append, acl:Control]. diff --git a/test/resources/accounts-strict-origin-off/alice/profile/card$.ttl b/test/resources/accounts-strict-origin-off/alice/profile/card$.ttl new file mode 100644 index 000000000..a014d960e --- /dev/null +++ b/test/resources/accounts-strict-origin-off/alice/profile/card$.ttl @@ -0,0 +1,4 @@ +@prefix acl: . + +<#me> acl:trustedApp [ acl:origin ; + acl:mode acl:Read, acl:Write, acl:Append, acl:Control].