Skip to content

Commit e8aad19

Browse files
authored
add owner to acl authorization (#1604)
* add owner to acl authorization * mashlib v1.7.2 * getOwner(req) * move getOwner() to ldp.js and add tests * add missing test files
1 parent 6538c61 commit e8aad19

File tree

14 files changed

+192
-59
lines changed

14 files changed

+192
-59
lines changed

lib/handlers/allow.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,17 @@ function allow (mode) {
4141
}
4242
// Obtain and store the ACL of the requested resource
4343
const resourceUrl = rootUrl + resourcePath
44-
req.acl = ACL.createFromLDPAndRequest(resourceUrl, ldp, req)
45-
4644
// Ensure the user has the required permission
4745
const userId = req.session.userId
48-
const isAllowed = await req.acl.can(userId, mode, req.method, stat)
49-
if (isAllowed) {
50-
return next()
51-
}
46+
try {
47+
req.acl = ACL.createFromLDPAndRequest(resourceUrl, ldp, req)
48+
49+
// if (resourceUrl.endsWith('.acl')) mode = 'Control'
50+
const isAllowed = await req.acl.can(userId, mode, req.method, stat)
51+
if (isAllowed) {
52+
return next()
53+
}
54+
} catch (error) { next(error) }
5255
if (mode === 'Read' && (resourcePath === '' || resourcePath === '/')) {
5356
// This is a hack to make NSS check the ACL for representation that is served for root (if any)
5457
// See https://github.com/solid/node-solid-server/issues/1063 for more info
@@ -68,6 +71,10 @@ function allow (mode) {
6871
}
6972
}
7073
}
74+
75+
// check user is owner. Find owner from /.meta
76+
if (resourceUrl.endsWith('.acl') && userId === await ldp.getOwner(req.hostname)) return next()
77+
7178
const error = req.authError || await req.acl.getError(userId, mode)
7279
debug(`${mode} access denied to ${userId || '(none)'}: ${error.status} - ${error.message}`)
7380
next(error)

lib/handlers/patch.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ const debug = require('../debug').handlers
88
const error = require('../http-error')
99
const $rdf = require('rdflib')
1010
const crypto = require('crypto')
11-
const overQuota = require('../utils').overQuota
12-
const getContentType = require('../utils').getContentType
11+
const { overQuota, getContentType } = require('../utils')
1312
const withLock = require('../lock')
1413

1514
// Patch parsers by request body content type
@@ -146,6 +145,10 @@ async function checkPermission (request, patchObject, resourceExists) {
146145
const allowed = await Promise.all(modes.map(mode => acl.can(userId, mode, request.method, resourceExists)))
147146
const allAllowed = allowed.reduce((memo, allowed) => memo && allowed, true)
148147
if (!allAllowed) {
148+
// check owner with Control
149+
const ldp = request.app.locals.ldp
150+
if (request.path.endsWith('.acl') && userId === await ldp.getOwner(request.hostname)) return Promise.resolve(patchObject)
151+
149152
const errors = await Promise.all(modes.map(mode => acl.getError(userId, mode)))
150153
const error = errors.filter(error => !!error)
151154
.reduce((prevErr, err) => prevErr.status > err.status ? prevErr : err, { status: 0 })

lib/header.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,13 @@ async function addPermissions (req, res, next) {
115115
if (!acl) return next()
116116

117117
// Turn permissions for the public and the user into a header
118-
const resource = req.app.locals.ldp.resourceMapper.resolveUrl(req.hostname, req.path)
119-
const [publicPerms, userPerms] = await Promise.all([
118+
const ldp = req.app.locals.ldp
119+
const resource = ldp.resourceMapper.resolveUrl(req.hostname, req.path)
120+
let [publicPerms, userPerms] = await Promise.all([
120121
getPermissionsFor(acl, null, req),
121122
getPermissionsFor(acl, session.userId, req)
122123
])
124+
if (resource.endsWith('.acl') && userPerms === '' && session.userId === await ldp.getOwner(req.hostname)) userPerms = 'control'
123125
debug.ACL(`Permissions on ${resource} for ${session.userId || '(none)'}: ${userPerms}`)
124126
debug.ACL(`Permissions on ${resource} for public: ${publicPerms}`)
125127
res.set('WAC-Allow', `user="${userPerms}",public="${publicPerms}"`)

lib/ldp.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,24 @@ class LDP {
433433
})
434434
}
435435

436+
// this is a hack to replace solid:owner, using solid:account in /.meta to avoid NSS migration
437+
// this /.meta has no functionality in actual NSS
438+
// comment https://github.com/solid/node-solid-server/pull/1604#discussion_r652903546
439+
async getOwner (hostname) {
440+
// const ldp = req.app.locals.ldp
441+
const rootUrl = this.resourceMapper.resolveUrl(hostname)
442+
let graph
443+
try {
444+
// TODO check for permission ?? Owner is a MUST
445+
graph = await this.getGraph(rootUrl + '/.meta')
446+
const SOLID = $rdf.Namespace('http://www.w3.org/ns/solid/terms#')
447+
const owner = await graph.any(null, SOLID('account'), $rdf.sym(rootUrl + '/'))
448+
return owner.uri
449+
} catch (error) {
450+
throw new Error(`Failed to get owner from ${rootUrl}/.meta, got ` + error)
451+
}
452+
}
453+
436454
async get (options, searchIndex = true) {
437455
let path, contentType, stats
438456
try {

package-lock.json

Lines changed: 29 additions & 29 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
@@ -85,7 +85,7 @@
8585
"ip-range-check": "0.2.0",
8686
"is-ip": "^3.1.0",
8787
"li": "^1.3.0",
88-
"mashlib": "^1.7.1",
88+
"mashlib": "^1.7.2",
8989
"mime-types": "^2.1.31",
9090
"negotiator": "^0.6.2",
9191
"node-fetch": "^2.6.1",

test/.meta

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Root Meta resource for the user account
2+
# Used to discover the account's WebID URI, given the account URI
3+
<https://tim.localhost:7777/profile/card#me>
4+
<http://www.w3.org/ns/solid/terms#account>
5+
</>.

test/integration/acl-oidc-test.js

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const request = require('request')
44
const path = require('path')
55
const { loadProvider, rm, checkDnsSettings, cleanDir } = require('../utils')
66
const IDToken = require('@solid/oidc-op/src/IDToken')
7-
const { clearAclCache } = require('../../lib/acl-checker')
7+
// const { clearAclCache } = require('../../lib/acl-checker')
88
const ldnode = require('../../index')
99

1010
const port = 7777
@@ -57,7 +57,7 @@ const argv = {
5757
}
5858

5959
// FIXME #1502
60-
describe.skip('ACL with WebID+OIDC over HTTP', function () {
60+
describe('ACL with WebID+OIDC over HTTP', function () {
6161
let ldp, ldpHttpsServer
6262

6363
before(checkDnsSettings)
@@ -80,9 +80,9 @@ describe.skip('ACL with WebID+OIDC over HTTP', function () {
8080
}).catch(console.error)
8181
})
8282

83-
afterEach(() => {
83+
/* afterEach(() => {
8484
clearAclCache()
85-
})
85+
}) */
8686

8787
after(() => {
8888
if (ldpHttpsServer) ldpHttpsServer.close()
@@ -138,17 +138,34 @@ describe.skip('ACL with WebID+OIDC over HTTP', function () {
138138
done()
139139
})
140140
})
141-
it('should not let edit the .acl', function (done) {
141+
it('user1 as solid:owner should let edit the .acl', function (done) {
142+
const options = createOptions('/empty-acl/.acl', 'user1', 'text/turtle')
143+
options.body = ''
144+
request.put(options, function (error, response, body) {
145+
assert.equal(error, null)
146+
assert.equal(response.statusCode, 201)
147+
done()
148+
})
149+
})
150+
it('user1 as solid:owner should let read the .acl', function (done) {
142151
const options = createOptions('/empty-acl/.acl', 'user1')
152+
request.get(options, function (error, response, body) {
153+
assert.equal(error, null)
154+
assert.equal(response.statusCode, 200)
155+
done()
156+
})
157+
})
158+
it('user2 should not let edit the .acl', function (done) {
159+
const options = createOptions('/empty-acl/.acl', 'user2', 'text/turtle')
143160
options.body = ''
144161
request.put(options, function (error, response, body) {
145162
assert.equal(error, null)
146163
assert.equal(response.statusCode, 403)
147164
done()
148165
})
149166
})
150-
it('should not let read the .acl', function (done) {
151-
const options = createOptions('/empty-acl/.acl', 'user1')
167+
it('user2 should not let read the .acl', function (done) {
168+
const options = createOptions('/empty-acl/.acl', 'user2')
152169
request.get(options, function (error, response, body) {
153170
assert.equal(error, null)
154171
assert.equal(response.statusCode, 403)
@@ -193,11 +210,11 @@ describe.skip('ACL with WebID+OIDC over HTTP', function () {
193210
})
194211
})
195212
it('Should not create empty acl file', function (done) {
196-
const options = createOptions('/write-acl/empty-acl/another-empty-folder/test-file.acl', 'user1')
213+
const options = createOptions('/write-acl/empty-acl/another-empty-folder/.acl', 'user1', 'text/turtle')
197214
options.body = ''
198215
request.put(options, function (error, response, body) {
199216
assert.equal(error, null)
200-
assert.equal(response.statusCode, 403)
217+
assert.equal(response.statusCode, 201) // 403) is this a must ?
201218
done()
202219
})
203220
})
@@ -210,11 +227,11 @@ describe.skip('ACL with WebID+OIDC over HTTP', function () {
210227
done()
211228
})
212229
})
213-
it('should fail as acl:default it used to try to authorize', function (done) {
230+
it('should fail as acl:default is used to try to authorize', function (done) {
214231
const options = createOptions('/write-acl/bad-acl-access/.acl', 'user1')
215232
request.get(options, function (error, response, body) {
216233
assert.equal(error, null)
217-
assert.equal(response.statusCode, 403)
234+
assert.equal(response.statusCode, 200) // 403) is this a must ?
218235
done()
219236
})
220237
})
@@ -240,7 +257,7 @@ describe.skip('ACL with WebID+OIDC over HTTP', function () {
240257
const options = createOptions('/write-acl/test-file.acl', 'user1')
241258
request.get(options, function (error, response, body) {
242259
assert.equal(error, null)
243-
assert.equal(response.statusCode, 403)
260+
assert.equal(response.statusCode, 200) // 403) is this a must ?
244261
done()
245262
})
246263
})
@@ -255,6 +272,37 @@ describe.skip('ACL with WebID+OIDC over HTTP', function () {
255272
})
256273
})
257274

275+
describe('no-control', function () {
276+
it('user1 as owner should edit acl file', function (done) {
277+
const options = createOptions('/no-control/.acl', 'user1', 'text/turtle')
278+
options.body = '<#0>' +
279+
'\n a <http://www.w3.org/ns/auth/acl#Authorization>;' +
280+
'\n <http://www.w3.org/ns/auth/acl#default> <https://tim.localhost:7777/no-control/> ;' +
281+
'\n <http://www.w3.org/ns/auth/acl#accessTo> <https://tim.localhost:7777/no-control/> ;' +
282+
'\n <http://www.w3.org/ns/auth/acl#agent> <https://tim.localhost:7777/profile/card#me> ;' +
283+
'\n <http://www.w3.org/ns/auth/acl#mode> <http://www.w3.org/ns/auth/acl#Read>.'
284+
request.put(options, function (error, response, body) {
285+
assert.equal(error, null)
286+
assert.equal(response.statusCode, 201)
287+
done()
288+
})
289+
})
290+
it('user2 should not edit acl file', function (done) {
291+
const options = createOptions('/no-control/.acl', 'user2', 'text/turtle')
292+
options.body = '<#0>' +
293+
'\n a <http://www.w3.org/ns/auth/acl#Authorization>;' +
294+
'\n <http://www.w3.org/ns/auth/acl#default> <https://tim.localhost:7777/no-control/> ;' +
295+
'\n <http://www.w3.org/ns/auth/acl#accessTo> <https://tim.localhost:7777/no-control/> ;' +
296+
'\n <http://www.w3.org/ns/auth/acl#agent> <https://tim.localhost:7777/profile/card#me> ;' +
297+
'\n <http://www.w3.org/ns/auth/acl#mode> <http://www.w3.org/ns/auth/acl#Read>.'
298+
request.put(options, function (error, response, body) {
299+
assert.equal(error, null)
300+
assert.equal(response.statusCode, 403)
301+
done()
302+
})
303+
})
304+
})
305+
258306
describe('Origin', function () {
259307
before(function () {
260308
rm('/accounts-acl/tim.localhost/origin/test-folder/.acl')

0 commit comments

Comments
 (0)