Skip to content

Commit 2959afb

Browse files
authored
Merge pull request #1067 from solid/bug/root-index-special-handling
Added special ACL handling for root
2 parents efdf8bc + f71e582 commit 2959afb

File tree

10 files changed

+126
-10
lines changed

10 files changed

+126
-10
lines changed

lib/acl-checker.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,8 @@ class ACLChecker {
113113
if (!returnAcl) {
114114
throw new HTTPError(500, `No ACL found for ${resource}, searched in \n- ${acls.join('\n- ')}`)
115115
}
116-
const groupUrls = returnAcl.graph
117-
.statementsMatching(null, ACL('agentGroup'), null)
118-
.map(node => node.object.value.split('#')[0])
116+
const groupNodes = returnAcl.graph.statementsMatching(null, ACL('agentGroup'), null)
117+
const groupUrls = groupNodes.map(node => node.object.value.split('#')[0])
119118
await Promise.all(groupUrls.map(groupUrl => {
120119
this.requests[groupUrl] = this.requests[groupUrl] || this.fetch(groupUrl, returnAcl.graph)
121120
return this.requests[groupUrl]

lib/handlers/allow.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,28 @@ function allow (mode, checkPermissionsForDirectory) {
4444
trustedOrigins.push(ldp.serverUri)
4545
}
4646
// Obtain and store the ACL of the requested resource
47-
req.acl = ACL.createFromLDPAndRequest(rootUrl + resourcePath, ldp, req)
47+
const resourceUrl = rootUrl + resourcePath
48+
req.acl = ACL.createFromLDPAndRequest(resourceUrl, ldp, req)
4849

4950
// Ensure the user has the required permission
5051
const userId = req.session.userId
5152
const isAllowed = await req.acl.can(userId, mode)
5253
if (isAllowed) {
5354
return next()
5455
}
56+
if (mode === 'Read' && (resourcePath === '' || resourcePath === '/')) {
57+
// This is a hack to make NSS check the ACL for representation that is served for root (if any)
58+
// See https://github.com/solid/node-solid-server/issues/1063 for more info
59+
const representationUrl = await ldp.resourceMapper.getRepresentationUrlForResource(resourceUrl)
60+
if (representationUrl.endsWith('index.html')) {
61+
// We ONLY want to do this when the representation we return is a HTML file
62+
req.acl = ACL.createFromLDPAndRequest(representationUrl, ldp, req)
63+
const representationIsAllowed = await req.acl.can(userId, mode)
64+
if (representationIsAllowed) {
65+
return next()
66+
}
67+
}
68+
}
5569
const error = await req.acl.getError(userId, mode)
5670
debug(`${mode} access denied to ${userId || '(none)'}: ${error.status} - ${error.message}`)
5771
next(error)

lib/resource-mapper.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,25 @@ class ResourceMapper {
9090
return { path, contentType: contentType || this._defaultContentType }
9191
}
9292

93+
async getRepresentationUrlForResource (resourceUrl) {
94+
let fullPath = this.getFullPath(resourceUrl)
95+
let isIndex = fullPath.endsWith('/')
96+
97+
// Append index filename if the URL ends with a '/'
98+
if (isIndex) {
99+
fullPath += this._indexFilename
100+
}
101+
102+
// Read all files in the corresponding folder
103+
const filename = fullPath.substr(fullPath.lastIndexOf('/') + 1)
104+
const folder = fullPath.substr(0, fullPath.length - filename.length)
105+
const files = await this._readdir(folder)
106+
107+
// Find a file with the same name (minus the dollar extension)
108+
let match = (files.find(f => this._removeDollarExtension(f) === filename || (isIndex && f.startsWith(this._indexFilename + '.'))))
109+
return `${resourceUrl}${match || ''}`
110+
}
111+
93112
// Maps a given server file to a URL
94113
async mapFileToUrl ({ path, hostname }) {
95114
// Determine the URL by chopping off everything after the dollar sign

test/integration/authentication-oidc-test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ describe('Authentication API (OIDC)', () => {
164164
describe('with that cookie and a non-matching origin', () => {
165165
let response
166166
before(done => {
167-
alice.get('/')
167+
alice.get('/private-for-owner.txt')
168168
.set('Cookie', cookie)
169169
.set('Origin', bobServerUri)
170170
.end((err, res) => {
@@ -234,7 +234,7 @@ describe('Authentication API (OIDC)', () => {
234234
describe('with that cookie and our origin', () => {
235235
let response
236236
before(done => {
237-
alice.get('/')
237+
alice.get('/private-for-owner.txt')
238238
.set('Cookie', cookie)
239239
.set('Origin', 'https://some.other.domain.com')
240240
.end((err, res) => {
@@ -252,7 +252,7 @@ describe('Authentication API (OIDC)', () => {
252252
describe('without that cookie but with our origin', () => {
253253
let response
254254
before(done => {
255-
alice.get('/')
255+
alice.get('/private-for-owner.txt')
256256
.set('Origin', aliceServerUri)
257257
.end((err, res) => {
258258
response = res
@@ -324,7 +324,7 @@ describe('Authentication API (OIDC)', () => {
324324
describe('without that cookie and a matching origin', () => {
325325
let response
326326
before(done => {
327-
alice.get('/')
327+
alice.get('/private-for-owner.txt')
328328
.set('Origin', bobServerUri)
329329
.end((err, res) => {
330330
response = res
@@ -341,7 +341,7 @@ describe('Authentication API (OIDC)', () => {
341341
describe('with that cookie and a non-matching origin', () => {
342342
let response
343343
before(done => {
344-
alice.get('/')
344+
alice.get('/private-for-owner.txt')
345345
.set('Cookie', cookie)
346346
.set('Origin', bobServerUri)
347347
.end((err, res) => {
@@ -360,7 +360,7 @@ describe('Authentication API (OIDC)', () => {
360360
let response
361361
before(done => {
362362
var malcookie = cookie.replace(/connect\.sid=(\S+)/, 'connect.sid=l33th4x0rzp0wn4g3;')
363-
alice.get('/')
363+
alice.get('/private-for-owner.txt')
364364
.set('Cookie', malcookie)
365365
.set('Origin', bobServerUri)
366366
.end((err, res) => {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const assert = require('chai').assert
2+
const request = require('request')
3+
const path = require('path')
4+
const { checkDnsSettings, cleanDir } = require('../utils')
5+
6+
const ldnode = require('../../index')
7+
8+
const port = 7777
9+
const serverUri = `https://localhost:${port}`
10+
const root = path.join(__dirname, '../resources/accounts-acl')
11+
const dbPath = path.join(root, 'db')
12+
const configPath = path.join(root, 'config')
13+
14+
function createOptions (path = '') {
15+
return {
16+
url: `https://nicola.localhost:${port}${path}`
17+
}
18+
}
19+
20+
describe('Special handling: Root ACL does not give READ access to root', () => {
21+
let ldp, ldpHttpsServer
22+
23+
before(checkDnsSettings)
24+
25+
before(done => {
26+
ldp = ldnode.createServer({
27+
root,
28+
serverUri,
29+
dbPath,
30+
port,
31+
configPath,
32+
sslKey: path.join(__dirname, '../keys/key.pem'),
33+
sslCert: path.join(__dirname, '../keys/cert.pem'),
34+
webid: true,
35+
multiuser: true,
36+
auth: 'oidc',
37+
strictOrigin: true,
38+
host: { serverUri }
39+
})
40+
ldpHttpsServer = ldp.listen(port, done)
41+
})
42+
43+
after(() => {
44+
if (ldpHttpsServer) ldpHttpsServer.close()
45+
cleanDir(root)
46+
})
47+
48+
describe('should still grant READ access to everyone because of index.html.acl', () => {
49+
it('for root with /', function (done) {
50+
var options = createOptions('/')
51+
request.get(options, function (error, response, body) {
52+
assert.equal(error, null)
53+
assert.equal(response.statusCode, 200)
54+
done()
55+
})
56+
})
57+
it('for root without /', function (done) {
58+
var options = createOptions()
59+
request.get(options, function (error, response, body) {
60+
assert.equal(error, null)
61+
assert.equal(response.statusCode, 200)
62+
done()
63+
})
64+
})
65+
})
66+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This ACL does nothing by default
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<body>Everyone should get READ access for this file through <pre>index.html.acl</pre>.</body>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This file grants everyone READ access to ./index.html
2+
3+
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
4+
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
5+
6+
<#public>
7+
a acl:Authorization;
8+
acl:agentClass foaf:Agent;
9+
acl:accessTo <./index.html>;
10+
acl:mode acl:Read.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
protected contents for owner
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<#Owner>
2+
a <http://www.w3.org/ns/auth/acl#Authorization> ;
3+
<http://www.w3.org/ns/auth/acl#accessTo> <./private-for-owner.txt>;
4+
<http://www.w3.org/ns/auth/acl#agent> <https://localhost:7000/profile/card#me>;
5+
<http://www.w3.org/ns/auth/acl#mode> <http://www.w3.org/ns/auth/acl#Read>, <http://www.w3.org/ns/auth/acl#Write>, <http://www.w3.org/ns/auth/acl#Control> .

0 commit comments

Comments
 (0)