Skip to content

Commit

Permalink
Merge branch 'appendPutNewDocument' into issue#1743
Browse files Browse the repository at this point in the history
  • Loading branch information
bourgeoa committed Dec 27, 2023
2 parents e17beb8 + 47100fc commit 0bbb3d3
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 11 deletions.
42 changes: 38 additions & 4 deletions lib/handlers/put.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async function handler (req, res, next) {
return next(e)
}
// check for valid rdf content for auxiliary resource and /profile/card
// in future we may check that /profile/card is a minimal valid WebID card
// TODO check that /profile/card is a minimal valid WebID card
if (isAuxiliary(req) || req.originalUrl === '/profile/card') {
if (contentType === 'text/turtle') {
return bodyParser.text({ type: () => true })(req, res, () => putValidRdf(req, res, next))
Expand All @@ -28,17 +28,51 @@ async function handler (req, res, next) {
return putStream(req, res, next)
}

// Verifies whether the user is allowed to perform Append PUT on the target
async function checkPermission (request, resourceExists) {
// If no ACL object was passed down, assume permissions are okay.
if (!request.acl) return Promise.resolve()
// At this point, we already assume append access,
// we might need to perform additional checks.
let modes = []
// acl:default Write is required for PUT when Resource Exists
if (resourceExists) modes = ['Write']
// if (resourceExists && request.originalUrl.endsWith('.acl')) modes = ['Control']
const { acl, session: { userId } } = request

const allowed = await Promise.all(modes.map(mode => acl.can(userId, mode, request.method, resourceExists)))
const allAllowed = allowed.reduce((memo, allowed) => memo && allowed, true)
if (!allAllowed) {
// check owner with Control
// const ldp = request.app.locals.ldp
// if (request.path.endsWith('.acl') && userId === await ldp.getOwner(request.hostname)) return Promise.resolve()

const errors = await Promise.all(modes.map(mode => acl.getError(userId, mode)))
const error = errors.filter(error => !!error)
.reduce((prevErr, err) => prevErr.status > err.status ? prevErr : err, { status: 0 })
return Promise.reject(error)
}
return Promise.resolve()
}

// TODO could be renamed as putResource (it now covers container and non-container)
async function putStream (req, res, next, stream = req) {
const ldp = req.app.locals.ldp
// try {
// Obtain details of the target resource
let resourceExists = true
try {
// First check if the file already exists
await ldp.resourceMapper.mapUrlToFile({ url: req })
} catch (err) {
resourceExists = false
}
try {
debug('test ' + req.get('content-type') + getContentType(req.headers))
if (!req.originalUrl.endsWith('.acl')) await checkPermission(req, resourceExists)
await ldp.put(req, stream, getContentType(req.headers))
debug('succeded putting the file/folder')
res.sendStatus(201)
return next()
} catch (err) {
debug('error putting the file/folder:' + err.message)
err.message = 'Can\'t write file/folder: ' + err.message
return next(err)
}
Expand Down
2 changes: 1 addition & 1 deletion lib/ldp-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function LdpMiddleware (corsSettings) {
router.get('/*', index, allow('Read'), header.addPermissions, get)
router.post('/*', allow('Append'), post)
router.patch('/*', allow('Append'), patch)
router.put('/*', allow('Write'), put)
router.put('/*', allow('Append'), put)
router.delete('/*', allow('Write'), del)

return router
Expand Down
45 changes: 39 additions & 6 deletions test/integration/acl-oidc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe('ACL with WebID+OIDC over HTTP', function () {
done()
})
})
it('user1 as solid:owner should let edit the .acl', function (done) {
it('user1 as solid:owner should let edit the .acl', function (done) { // alain
const options = createOptions('/empty-acl/.acl', 'user1', 'text/turtle')
options.body = ''
request.put(options, function (error, response, body) {
Expand Down Expand Up @@ -209,7 +209,7 @@ describe('ACL with WebID+OIDC over HTTP', function () {
done()
})
})
it('Should not create empty acl file', function (done) {
it('Should not create empty acl file', function (done) { // alain
const options = createOptions('/write-acl/empty-acl/another-empty-folder/.acl', 'user1', 'text/turtle')
options.body = ''
request.put(options, function (error, response, body) {
Expand Down Expand Up @@ -273,7 +273,7 @@ describe('ACL with WebID+OIDC over HTTP', function () {
})

describe('no-control', function () {
it('user1 as owner should edit acl file', function (done) {
it('user1 as owner should edit acl file', function (done) { // alain
const options = createOptions('/no-control/.acl', 'user1', 'text/turtle')
options.body = '<#0>' +
'\n a <http://www.w3.org/ns/auth/acl#Authorization>;' +
Expand Down Expand Up @@ -571,6 +571,27 @@ describe('ACL with WebID+OIDC over HTTP', function () {
done()
})
})
it('user1 should be able to PUT (which CREATEs) (non existent resource)', function (done) {
const options = createOptions('/append-inherited/test1.ttl', 'user1')
options.body = '<a> <b> <c> .\n'
options.headers['content-type'] = 'text/turtle'
request.put(options, function (error, response, body) {
assert.equal(error, null)
assert.equal(response.statusCode, 201)
done()
})
})
it('user2 should not be able to PUT with Append (existing resource)', function (done) {
const options = createOptions('/append-inherited/test1.ttl', 'user2')
options.body = '<a> <b> <c> .\n'
options.headers['content-type'] = 'text/turtle'
request.put(options, function (error, response, body) {
assert.equal(error, null)
assert.equal(response.statusCode, 403)
assert.include(response.statusMessage, 'User Unauthorized')
done()
})
})
it('user1 should be able to access test file', function (done) {
const options = createOptions('/append-acl/abc.ttl', 'user1')
request.head(options, function (error, response, body) {
Expand Down Expand Up @@ -599,6 +620,16 @@ describe('ACL with WebID+OIDC over HTTP', function () {
done()
})
})
it('user2 should be able to PUT to (which CREATEs) a non existent resource', function (done) { // alain
const options = createOptions('/append-inherited/new1.ttl', 'user1')
options.body = '<a> <b> <c> .\n'
options.headers['content-type'] = 'text/turtle'
request.put(options, function (error, response, body) {
assert.equal(error, null)
assert.equal(response.statusCode, 201)
done()
})
})
it('user2 should not be able to access test file\'s ACL file', function (done) {
const options = createOptions('/append-acl/abc.ttl.acl', 'user2', 'text/turtle')
request.head(options, function (error, response, body) {
Expand Down Expand Up @@ -627,13 +658,13 @@ describe('ACL with WebID+OIDC over HTTP', function () {
done()
})
})
it('user2 (with append permission) cannot use PUT to append', function (done) {
it('user2 (with append permission) cannot use PUT on an existing resource', function (done) {
const options = createOptions('/append-acl/abc.ttl', 'user2', 'text/turtle')
options.body = '<d> <e> <f> .\n'
request.put(options, function (error, response, body) {
assert.equal(error, null)
assert.equal(response.statusCode, 403)
assert.equal(response.statusMessage, 'User Unauthorized')
assert.include(response.statusMessage, 'Can\'t write file/folder: User Unauthorized')
done()
})
})
Expand All @@ -652,13 +683,15 @@ describe('ACL with WebID+OIDC over HTTP', function () {
request.put(options, function (error, response, body) {
assert.equal(error, null)
assert.equal(response.statusCode, 401)
assert.equal(response.statusMessage, 'Unauthenticated')
assert.include(response.statusMessage, 'Can\'t write file/folder: Unauthenticated')
done()
})
})
after(function () {
rm('/accounts-acl/tim.localhost/append-inherited/test.ttl')
rm('/accounts-acl/tim.localhost/append-inherited/test1.ttl')
rm('/accounts-acl/tim.localhost/append-inherited/new.ttl')
rm('/accounts-acl/tim.localhost/append-inherited/new1.ttl')
})
})

Expand Down

0 comments on commit 0bbb3d3

Please sign in to comment.