Skip to content

Commit 82ad56f

Browse files
megothrubensworks
authored andcommitted
Trying another approach to acl.can
Not throwing errors, but caching error messages for later use
1 parent 2740f88 commit 82ad56f

File tree

3 files changed

+143
-74
lines changed

3 files changed

+143
-74
lines changed

lib/acl-checker.js

Lines changed: 120 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict'
22

3-
const PermissionSet = require('solid-permissions').PermissionSet
3+
// const PermissionSet = require('solid-permissions').PermissionSet
44
const rdf = require('rdflib')
55
const debug = require('./debug').ACL
66
const HTTPError = require('./http-error')
@@ -14,32 +14,29 @@ class ACLChecker {
1414
constructor (resource, options = {}) {
1515
this.resource = resource
1616
this.host = options.host
17-
this.origin = options.origin
17+
this.agentOrigin = options.origin
1818
this.fetch = options.fetch
1919
this.fetchGraph = options.fetchGraph
2020
this.strictOrigin = options.strictOrigin
2121
this.trustedOrigins = options.trustedOrigins
2222
this.suffix = options.suffix || DEFAULT_ACL_SUFFIX
23+
this.aclCached = {}
24+
this.messagesCached = {}
2325
}
2426

2527
// Returns a fulfilled promise when the user can access the resource
2628
// in the given mode, or rejects with an HTTP error otherwise
2729
async can (user, mode) {
30+
const cacheKey = `${mode}-${user}`
31+
if (this.aclCached[cacheKey]) {
32+
return this.aclCached[cacheKey]
33+
}
34+
this.messagesCached[cacheKey] = this.messagesCached[cacheKey] || []
2835
// If this is an ACL, Control mode must be present for any operations
2936
if (this.isAcl(this.resource)) {
3037
mode = 'Control'
3138
}
3239

33-
// Obtain the permission set for the resource
34-
// this.acl.graph
35-
// this.resource
36-
// this.acl.isContainer ? this.resource : null
37-
// this.acl.acl
38-
// user
39-
// ACL(mode)
40-
// this.origin
41-
// this.trustedOrigins
42-
4340
// console.log('ACL', this.origin, this.trustedOrigins)
4441
// console.log(aclCheck.accessDenied)
4542
// if (!this._permissionSet) {
@@ -50,30 +47,56 @@ class ACLChecker {
5047
// aclCheck.checkAccess(acl.graph, this.resource)
5148

5249
// Check the resource's permissions
50+
<<<<<<< 2740f8873bfe7d7edcf0c2c31f927a106dc0abc7
5351
this.acl = this.acl || await this.getNearestACL().catch(err => {
5452
throw new HTTPError(500, `Found no ACL file:\n${err}`)
5553
})
54+
=======
55+
const acl = await this.getNearestACL()
56+
.catch(err => {
57+
this.messagesCached[cacheKey].push(new HTTPError(500, err))
58+
})
59+
if (!acl) {
60+
this.aclCached[cacheKey] = Promise.resolve(false)
61+
return this.aclCached[cacheKey]
62+
}
63+
>>>>>>> Trying another approach to acl.can
5664
// console.log('TEST', this.acl)
5765
const resource = rdf.sym(this.resource)
58-
// const directory = this.acl.isContainer ? this.resource : null
59-
const directory = this.acl.isContainer ? rdf.sym(ACLChecker.getDirectory(this.acl.acl)) : null
60-
// console.log(ACLChecker.getDirectory(this.acl.acl))
61-
const aclFile = rdf.sym(this.acl.acl)
66+
// const directory = acl.isContainer ? this.resource : null
67+
const directory = acl.isContainer ? rdf.sym(ACLChecker.getDirectory(acl.acl)) : null
68+
// console.log(ACLChecker.getDirectory(acl.acl))
69+
const aclFile = rdf.sym(acl.acl)
6270
// const agent = rdf.sym(user)
6371
const agent = user ? rdf.sym(user) : null
6472
// console.log('ACL agent', agent)
65-
// console.log('ACL FILE', this.resource, this.acl.acl)
73+
// console.log('ACL FILE', this.resource, acl.acl)
6674
const modes = [ACL(mode)]
67-
const origin = this.origin ? rdf.sym(this.origin) : null
75+
const agentOrigin = this.agentOrigin ? rdf.sym(this.agentOrigin) : null
6876
const trustedOrigins = this.trustedOrigins ? this.trustedOrigins.map(trustedOrigin => rdf.sym(trustedOrigin)) : null
69-
const accessDenied = aclCheck.accessDenied(this.acl.graph, resource, directory, aclFile, agent, modes, origin, trustedOrigins)
70-
console.log('ACCESS DENIED', accessDenied, '\n\n')
77+
const accessDenied = aclCheck.accessDenied(acl.graph, resource, directory, aclFile, agent, modes, agentOrigin, trustedOrigins)
78+
console.log('BAR', accessDenied)
7179
if (accessDenied && user) {
80+
<<<<<<< 2740f8873bfe7d7edcf0c2c31f927a106dc0abc7
7281
throw new HTTPError(403, accessDenied)
7382
} else if (accessDenied) {
7483
throw new HTTPError(401, 'Unauthenticated')
84+
=======
85+
this.messagesCached[cacheKey].push(new HTTPError(403, `Access to ${this.resource} denied for ${user}: ${accessDenied}`))
86+
} else if (accessDenied) {
87+
this.messagesCached[cacheKey].push(new HTTPError(401, `Access to ${this.resource} requires authorization: ${accessDenied}`))
88+
>>>>>>> Trying another approach to acl.can
7589
}
76-
return Promise.resolve(true)
90+
console.log('ACCESS ALLOWED', !accessDenied, user, '\n\n')
91+
this.aclCached[cacheKey] = Promise.resolve(!accessDenied)
92+
return this.aclCached
93+
}
94+
95+
async getError (mode, user) {
96+
const cacheKey = `${mode}-${user}`
97+
this.aclCached[cacheKey] = this.aclCached[cacheKey] || this.can(user, mode)
98+
const isAllowed = await this.aclCached[cacheKey]
99+
return isAllowed ? null : this.messagesCached[cacheKey].reduce((prevMsg, msg) => msg.status > prevMsg.status ? msg : prevMsg, { status: 0 })
77100
}
78101

79102
// return Promise.resolve(true)
@@ -93,13 +116,14 @@ class ACLChecker {
93116
return `${parts.join('/')}/`
94117
}
95118

96-
// Gets the ACL that applies to the resource
119+
// Gets the ACL that applies to the resource
97120
async getNearestACL () {
98121
const { resource } = this
99122
let isContainer = false
100123
// let directory = null
101124
// Create a cascade of reject handlers (one for each possible ACL)
102125
const possibleACLs = this.getPossibleACLs()
126+
<<<<<<< 2740f8873bfe7d7edcf0c2c31f927a106dc0abc7
103127
const nearestACL = possibleACLs.reduce((prevACL, acl) => {
104128
return prevACL.catch(() => new Promise((resolve, reject) => {
105129
this.fetch(acl, (err, graph) => {
@@ -118,6 +142,47 @@ class ACLChecker {
118142
}))
119143
}, Promise.reject())
120144
return nearestACL.catch(e => { throw new Error(`No ACL resource found, searched in \n- ${possibleACLs.join('\n- ')}`) })
145+
=======
146+
const acls = [...possibleACLs]
147+
let returnAcl = null
148+
while (possibleACLs.length > 0 && !returnAcl) {
149+
const acl = possibleACLs.shift()
150+
try {
151+
const graph = await this.fetch(acl)
152+
const relative = resource.replace(acl.replace(/[^/]+$/, ''), './')
153+
debug(`Using ACL ${acl} for ${relative}`)
154+
returnAcl = { acl, graph, isContainer }
155+
} catch (err) {
156+
if (err && err.code === 'ENOENT') {
157+
isContainer = true
158+
return
159+
} else if (err) {
160+
console.error('ERROR IN getNearestACL', err)
161+
debug(err)
162+
throw err
163+
}
164+
}
165+
}
166+
if (!returnAcl) {
167+
throw new Error(`No ACL found for ${resource}, searched in \n- ${acls.join('\n- ')}`)
168+
}
169+
return returnAcl
170+
// const nearestACL = possibleACLs.reduce((prevACL, acl) => {
171+
// return prevACL.catch(() => new Promise((resolve, reject) => {
172+
// this.fetch(acl, (err, graph) => {
173+
// if (err && err.code !== 'ENOENT') {
174+
// isContainer = true
175+
// reject(err)
176+
// } else {
177+
// const relative = resource.replace(acl.replace(/[^/]+$/, ''), './')
178+
// debug(`Using ACL ${acl} for ${relative}`)
179+
// resolve({ acl, graph, isContainer })
180+
// }
181+
// })
182+
// }))
183+
// }, Promise.reject())
184+
// return nearestACL.catch(e => { throw new Error(`No ACL resource found, searched in \n- ${possibleACLs.join('\n- ')}`) })
185+
>>>>>>> Trying another approach to acl.can
121186
}
122187

123188
// Gets all possible ACL paths that apply to the resource
@@ -140,41 +205,41 @@ class ACLChecker {
140205
}
141206

142207
// Tests whether the permissions allow a given operation
143-
checkAccess (permissionSet, user, mode) {
144-
const options = { fetchGraph: this.fetchGraph }
145-
return permissionSet.checkAccess(this.resource, user, mode, options)
146-
.then(hasAccess => {
147-
if (hasAccess) {
148-
return true
149-
} else {
150-
throw new Error('ACL file found but no matching policy found')
151-
}
152-
})
153-
}
208+
// checkAccess (permissionSet, user, mode) {
209+
// const options = { fetchGraph: this.fetchGraph }
210+
// return permissionSet.checkAccess(this.resource, user, mode, options)
211+
// .then(hasAccess => {
212+
// if (hasAccess) {
213+
// return true
214+
// } else {
215+
// throw new Error('ACL file found but no matching policy found')
216+
// }
217+
// })
218+
// }
154219

155220
// Gets the permission set for the given ACL
156-
getPermissionSet ({ acl, graph, isContainer }) {
157-
if (!graph || graph.length === 0) {
158-
debug('ACL ' + acl + ' is empty')
159-
throw new Error('No policy found - empty ACL')
160-
}
161-
const aclOptions = {
162-
aclSuffix: this.suffix,
163-
graph: graph,
164-
host: this.host,
165-
origin: this.origin,
166-
rdf: rdf,
167-
strictOrigin: this.strictOrigin,
168-
trustedOrigins: this.trustedOrigins,
169-
isAcl: uri => this.isAcl(uri),
170-
aclUrlFor: uri => this.aclUrlFor(uri)
171-
}
172-
return new PermissionSet(this.resource, acl, isContainer, aclOptions)
173-
}
174-
175-
aclUrlFor (uri) {
176-
return this.isAcl(uri) ? uri : uri + this.suffix
177-
}
221+
// getPermissionSet ({ acl, graph, isContainer }) {
222+
// if (!graph || graph.length === 0) {
223+
// debug('ACL ' + acl + ' is empty')
224+
// throw new Error('No policy found - empty ACL')
225+
// }
226+
// const aclOptions = {
227+
// aclSuffix: this.suffix,
228+
// graph: graph,
229+
// host: this.host,
230+
// origin: this.origin,
231+
// rdf: rdf,
232+
// strictOrigin: this.strictOrigin,
233+
// trustedOrigins: this.trustedOrigins,
234+
// isAcl: uri => this.isAcl(uri),
235+
// aclUrlFor: uri => this.aclUrlFor(uri)
236+
// }
237+
// return new PermissionSet(this.resource, acl, isContainer, aclOptions)
238+
// }
239+
240+
// aclUrlFor (uri) {
241+
// return this.isAcl(uri) ? uri : uri + this.suffix
242+
// }
178243

179244
isAcl (resource) {
180245
return resource.endsWith(this.suffix)

lib/handlers/allow.js

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,26 +36,29 @@ function allow (mode) {
3636
// Obtain and store the ACL of the requested resource
3737
req.acl = new ACL(rootUrl + reqPath, {
3838
origin: req.get('origin'),
39-
host: req.get('host'),
39+
// host: req.get('host'),
4040
fetch: fetchFromLdp(ldp.resourceMapper, ldp),
41-
fetchGraph: (uri, options) => {
42-
// first try loading from local fs
43-
return ldp.getGraph(uri, options.contentType)
44-
// failing that, fetch remote graph
45-
.catch(() => ldp.fetchGraph(uri, options))
46-
},
41+
// fetchGraph: (uri, options) => {
42+
// // first try loading from local fs
43+
// return ldp.getGraph(uri, options.contentType)
44+
// // failing that, fetch remote graph
45+
// .catch(() => ldp.fetchGraph(uri, options))
46+
// },
4747
suffix: ldp.suffixAcl,
4848
strictOrigin: ldp.strictOrigin,
4949
trustedOrigins: ldp.trustedOrigins
5050
})
5151

5252
// Ensure the user has the required permission
5353
const userId = req.session.userId
54-
req.acl.can(userId, mode)
55-
.then(() => next(), err => {
56-
debug(`${mode} access denied to ${userId || '(none)'}`)
57-
next(err)
58-
})
54+
const isAllowed = await req.acl.can(userId, mode)
55+
if (isAllowed) {
56+
return next()
57+
}
58+
const error = await req.acl.getError(mode, userId)
59+
console.log('ERROR', error)
60+
debug(`${mode} access denied to ${userId || '(none)'}: ${error.status} - ${error.message}`)
61+
res.status(error.status).send(error.message)
5962
}
6063
}
6164

@@ -68,7 +71,7 @@ function allow (mode) {
6871
* @return {Function} Returns a `fetch(uri, callback)` handler
6972
*/
7073
function fetchFromLdp (mapper, ldp) {
71-
return function fetch (url, callback) {
72-
ldp.getGraph(url).then(g => callback(null, g), callback)
74+
return async function fetch (url) {
75+
return ldp.getGraph(url)
7376
}
7477
}

lib/header.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ function addPermissions (req, res, next) {
117117
// Turn permissions for the public and the user into a header
118118
const resource = req.app.locals.ldp.resourceMapper.resolveUrl(req.hostname, req.path)
119119
Promise.all([
120-
getPermissionsFor(acl, null, resource),
121-
getPermissionsFor(acl, session.userId, resource)
120+
getPermissionsFor(acl, null, req),
121+
getPermissionsFor(acl, session.userId, req)
122122
])
123123
.then(([publicPerms, userPerms]) => {
124124
debug.ACL(`Permissions on ${resource} for ${session.userId || '(none)'}: ${userPerms}`)
@@ -129,7 +129,8 @@ function addPermissions (req, res, next) {
129129
}
130130

131131
// Gets the permissions string for the given user and resource
132-
function getPermissionsFor (acl, user, resource) {
133-
return Promise.all(MODES.map(mode => acl.can(user, mode).catch(e => false)))
134-
.then(allowed => PERMISSIONS.filter((_, i) => allowed[i]).join(' '))
132+
function getPermissionsFor (acl, user, req) {
133+
const accesses = MODES.map(mode => acl.can(user, mode))
134+
return Promise.all(accesses)
135+
.then(allowed => PERMISSIONS.filter((_, i) => allowed[i]).join(' '))
135136
}

0 commit comments

Comments
 (0)