Skip to content

Commit 6929c05

Browse files
authored
Merge pull request #18 from solid/feature/trusted-app
Adding new method getTrustedModesForOrigin and added optional parameter originTrustedModes to accessDenied and checkAccess
2 parents df7eb09 + f9b8efa commit 6929c05

File tree

6 files changed

+216
-32
lines changed

6 files changed

+216
-32
lines changed

package-lock.json

Lines changed: 7 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"postversion": "git push --follow-tags",
1616
"prepublish": "npm test && npm run build",
1717
"standard": "standard *.js src/*.js",
18-
"tape": "tape test/unit/check-access-test.js test/unit/access-denied-test.js test/unit/configure-logger-test.js",
18+
"tape": "tape test/unit/check-access-test.js test/unit/access-denied-test.js test/unit/configure-logger-test.js test/unit/get-trusted-modes-for-origin-test.js",
1919
"test": "npm run standard && npm run tape"
2020
},
2121
"repository": {

src/acl-check.js

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ function publisherTrustedApp (kb, doc, aclDoc, modesRequired, origin, docAuths)
2020
// modesRequired.every(mode => appAuths.some(auth => kb.holds(auth, ACL('mode'), mode, aclDoc)))
2121
}
2222

23-
function accessDenied (kb, doc, directory, aclDoc, agent, modesRequired, origin, trustedOrigins) {
23+
function accessDenied (kb, doc, directory, aclDoc, agent, modesRequired, origin, trustedOrigins, originTrustedModes = []) {
2424
log(`accessDenied: checking access to ${doc} by ${agent} and origin ${origin}`)
25-
let modeURIorReasons = modesAllowed(kb, doc, directory, aclDoc, agent, origin, trustedOrigins)
25+
const modeURIorReasons = modesAllowed(kb, doc, directory, aclDoc, agent, origin, trustedOrigins, originTrustedModes)
2626
let ok = false
2727
log('accessDenied: modeURIorReasons: ' + JSON.stringify(Array.from(modeURIorReasons)))
2828
modesRequired.forEach(mode => {
@@ -44,15 +44,70 @@ function accessDenied (kb, doc, directory, aclDoc, agent, modesRequired, origin,
4444
return ok
4545
}
4646

47+
async function getTrustedModesForOrigin (kb, doc, directory, aclDoc, origin, fetch) {
48+
// FIXME: this is duplicate code from the modesAllowed function, will refactor,
49+
// see https://github.com/solid/acl-check/issues/22
50+
var auths
51+
if (!directory) { // Normal case, ACL for a file
52+
auths = kb.each(null, ACL('accessTo'), doc, aclDoc)
53+
log(` ${auths.length} direct authentications about ${doc}`)
54+
} else {
55+
auths = kb.each(null, ACL('default'), directory, null)
56+
auths = auths.concat(kb.each(null, ACL('defaultForNew'), directory, null)) // Deprecated but keep for ages
57+
log(` ${auths.length} default authentications about ${directory} in ${aclDoc}`)
58+
}
59+
const ownerAuths = auths.filter(auth => kb.holds(auth, ACL('mode'), ACL('Control'), aclDoc))
60+
const owners = ownerAuths.reduce((acc, auth) => acc.concat(kb.each(auth, ACL('agent'))), []) // owners
61+
let result
62+
try {
63+
result = await Promise.all(owners.map(owner => {
64+
return fetch(owner).then(() => {
65+
const q = `
66+
SELECT ?mode WHERE {
67+
${owner} ${ACL('trustedApp')} ?trustedOrigin.
68+
?trustedOrigin ${ACL('origin')} ${origin};
69+
${ACL('mode')} ?mode .
70+
}`
71+
return query(q, kb)
72+
}).catch(e => {
73+
log('could not fetch owner doc', owner, e.message)
74+
})
75+
}))
76+
} catch (e) {
77+
log('error checking owner profiles', e.message)
78+
}
79+
let trustedModes = []
80+
result.map(ownerResults => ownerResults.map(entry => {
81+
trustedModes.push(entry['?mode'])
82+
}))
83+
return Promise.resolve(trustedModes)
84+
}
85+
86+
async function query (queryString, store) {
87+
return new Promise((resolve, reject) => {
88+
try {
89+
const query = $rdf.SPARQLToQuery(queryString, true, store)
90+
const results = []
91+
store.query(query, (result) => {
92+
results.push(result)
93+
}, null, () => {
94+
resolve(results)
95+
})
96+
} catch (err) {
97+
reject(err)
98+
}
99+
})
100+
}
101+
47102
/* Function checkAccess
48103
** @param kb A quadstore
49104
** @param doc the resource (A named node) or directory for which ACL applies
50105
*/
51-
function checkAccess (kb, doc, directory, aclDoc, agent, modesRequired, origin, trustedOrigins) {
52-
return !accessDenied(kb, doc, directory, aclDoc, agent, modesRequired, origin, trustedOrigins)
106+
function checkAccess (kb, doc, directory, aclDoc, agent, modesRequired, origin, trustedOrigins, originTrustedModes) {
107+
return !accessDenied(kb, doc, directory, aclDoc, agent, modesRequired, origin, trustedOrigins, originTrustedModes)
53108
}
54109

55-
function modesAllowed (kb, doc, directory, aclDoc, agent, origin, trustedOrigins) {
110+
function modesAllowed (kb, doc, directory, aclDoc, agent, origin, trustedOrigins, originTrustedModes = []) {
56111
var auths
57112
if (!directory) { // Normal case, ACL for a file
58113
auths = kb.each(null, ACL('accessTo'), doc, aclDoc)
@@ -62,7 +117,7 @@ function modesAllowed (kb, doc, directory, aclDoc, agent, origin, trustedOrigins
62117
auths = auths.concat(kb.each(null, ACL('defaultForNew'), directory, null)) // Deprecated but keep for ages
63118
log(` ${auths.length} default authentications about ${directory} in ${aclDoc}`)
64119
}
65-
if (origin && trustedOrigins && trustedOriginsIncludeOrigin(trustedOrigins, origin)) {
120+
if (origin && trustedOrigins && nodesIncludeNode(trustedOrigins, origin)) {
66121
log('Origin ' + origin + ' is trusted')
67122
origin = null // stop worrying about origin
68123
log(` modesAllowed: Origin ${origin} is trusted.`)
@@ -108,6 +163,10 @@ function modesAllowed (kb, doc, directory, aclDoc, agent, origin, trustedOrigins
108163
log(' Origin check not needed: no origin.')
109164
return false
110165
}
166+
if (originTrustedModes && originTrustedModes.length > 0) {
167+
log(` Origin might have access (${originTrustedModes.join(', ')})`)
168+
return false
169+
}
111170
if (originOK(auth, origin)) {
112171
log(' Origin check succeeded.')
113172
return false
@@ -125,6 +184,9 @@ function modesAllowed (kb, doc, directory, aclDoc, agent, origin, trustedOrigins
125184
modeURIorReasons.add(agentAndAppStatus)
126185
} else {
127186
let modes = kb.each(auth, ACL('mode'), null, aclDoc)
187+
if (originTrustedModes && originTrustedModes.length > 0) {
188+
modes = modes.filter(mode => nodesIncludeNode(originTrustedModes, mode))
189+
}
128190
modes.forEach(mode => {
129191
log(' Mode allowed: ' + mode)
130192
modeURIorReasons.add(mode.uri)
@@ -134,9 +196,8 @@ function modesAllowed (kb, doc, directory, aclDoc, agent, origin, trustedOrigins
134196
return modeURIorReasons
135197
}
136198

137-
function trustedOriginsIncludeOrigin (trustedOrigins, origin) {
138-
return trustedOrigins.filter(
139-
trustedOrigin => trustedOrigin.termType === origin.termType && trustedOrigin.value === origin.value).length > 0
199+
function nodesIncludeNode (nodes, node) {
200+
return nodes.some(trustedOrigin => trustedOrigin.termType === node.termType && trustedOrigin.value === node.value)
140201
}
141202

142203
function configureLogger (logger) {
@@ -147,9 +208,10 @@ function log (...msgs) {
147208
return (_logger || console.log).apply(_logger, msgs)
148209
}
149210

211+
module.exports.accessDenied = accessDenied
150212
module.exports.checkAccess = checkAccess
151213
module.exports.configureLogger = configureLogger
214+
module.exports.getTrustedModesForOrigin = getTrustedModesForOrigin
152215
module.exports.log = log
153-
module.exports.accessDenied = accessDenied
154216
module.exports.modesAllowed = modesAllowed
155217
module.exports.publisherTrustedApp = publisherTrustedApp

test/unit/access-denied-test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const $rdf = require('rdflib')
66

77
const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#')
88
const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/')
9+
const ALICE = $rdf.Namespace('https://alice.example.com/')
910

1011
const prefixes = `@prefix acl: <http://www.w3.org/ns/auth/acl#> .
1112
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
@@ -403,3 +404,34 @@ test('aclCheck accessDenied() test - With trustedOrigins', t => {
403404
t.end()
404405
})
405406

407+
test('aclCheck accessDenied() test - with use of originTrustedModes', t => {
408+
const resource = ALICE('docs/file1')
409+
const aclDoc = ALICE('docs/.acl')
410+
const aclUrl = aclDoc.uri
411+
412+
const origin = $rdf.sym('https://apps.example.com')
413+
const aclStore = $rdf.graph()
414+
// grants read, write and control access to Alice
415+
const ACLtext = `${prefixes}
416+
<#auth> a acl:Authorization;
417+
acl:mode acl:Read, acl:Write, acl:Control;
418+
acl:agent alice:me;
419+
acl:accessTo ${resource} .
420+
`
421+
$rdf.parse(ACLtext, aclStore, aclUrl, 'text/turtle')
422+
423+
const agent = alice
424+
const directory = null
425+
const trustedOrigins = []
426+
const originTrustedModes = [ACL('Read'), ACL('Write')]
427+
428+
const readWriteModeRequired = [ACL('Read'), ACL('Write')]
429+
const readWriteModeResult = aclLogic.accessDenied(aclStore, resource, directory, aclDoc, agent, readWriteModeRequired, origin, trustedOrigins, originTrustedModes)
430+
t.ok(!readWriteModeResult, 'Should get access to modes when origin is listed as trusted app')
431+
432+
const controlModeRequired = [ACL('Control')]
433+
const controlModeResult = aclLogic.accessDenied(aclStore, resource, directory, aclDoc, agent, controlModeRequired, origin, trustedOrigins, originTrustedModes)
434+
t.ok(controlModeResult, 'All Required Access Modes Not Granted', 'Correct reason')
435+
436+
t.end()
437+
})

test/unit/check-access-test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const $rdf = require('rdflib')
99

1010
const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#')
1111
const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/')
12+
const ALICE = $rdf.Namespace('https://alice.example.com/')
1213

1314
const prefixes = `@prefix acl: <http://www.w3.org/ns/auth/acl#> .
1415
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
@@ -327,3 +328,35 @@ test('acl-check checkAccess() test - default/inherited', function (t) {
327328

328329
t.end()
329330
})
331+
332+
test('aclCheck checkAccess() test - with use of originTrustedModes', t => {
333+
const resource = ALICE('docs/file1')
334+
const aclDoc = ALICE('docs/.acl')
335+
const aclUrl = aclDoc.uri
336+
337+
const origin = $rdf.sym('https://apps.example.com')
338+
const aclStore = $rdf.graph()
339+
// grants read, write and control access to Alice
340+
const ACLtext = `${prefixes}
341+
<#auth> a acl:Authorization;
342+
acl:mode acl:Read, acl:Write, acl:Control;
343+
acl:agent alice:me;
344+
acl:accessTo ${resource} .
345+
`
346+
$rdf.parse(ACLtext, aclStore, aclUrl, 'text/turtle')
347+
348+
const agent = alice
349+
const directory = null
350+
const trustedOrigins = []
351+
const originTrustedModes = [ACL('Read'), ACL('Write')]
352+
353+
const readWriteModeRequired = [ACL('Read'), ACL('Write')]
354+
const readWriteModeResult = aclLogic.checkAccess(aclStore, resource, directory, aclDoc, agent, readWriteModeRequired, origin, trustedOrigins, originTrustedModes)
355+
t.ok(readWriteModeResult, 'Should get access to modes when origin is listed as trusted app')
356+
357+
const controlModeRequired = [ACL('Control')]
358+
const controlModeResult = aclLogic.checkAccess(aclStore, resource, directory, aclDoc, agent, controlModeRequired, origin, trustedOrigins, originTrustedModes)
359+
t.ok(!controlModeResult, 'All Required Access Modes Not Granted', 'Correct reason')
360+
361+
t.end()
362+
})

0 commit comments

Comments
 (0)