forked from nodeSolidServer/node-solid-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathacl.js
More file actions
126 lines (104 loc) · 3.72 KB
/
acl.js
File metadata and controls
126 lines (104 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { createRequire } from 'module'
import fs from 'fs/promises'
import { $rdf } from './rdf.js'
import { HttpError } from './error.js'
import { debugACL as debug } from './utils.js'
const require = createRequire(import.meta.url)
const aclCheck = require('@solid/acl-check')
const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#')
export class ACLChecker {
constructor ({ ldp, rootUrl }) {
this.ldp = ldp
this.rootUrl = rootUrl
}
async can (user, resourceUrl, mode, isContainer) {
const { aclUrl, aclBody, isDefault, directory } = await this.findACL(resourceUrl, isContainer)
if (!aclBody) {
throw new HttpError(500, 'No ACL found for ' + resourceUrl)
}
const graph = $rdf.graph()
try {
$rdf.parse(aclBody, graph, aclUrl, 'text/turtle')
} catch (e) {
throw new HttpError(500, 'Error parsing ACL: ' + e.message)
}
const doc = $rdf.sym(resourceUrl)
const aclDoc = $rdf.sym(aclUrl)
const agent = user ? $rdf.sym(user) : null
const modes = [ACL(mode)]
const dir = isDefault ? $rdf.sym(directory) : null
const denied = aclCheck.accessDenied(graph, doc, dir, aclDoc, agent, modes, null, null)
if (denied) {
debug(`Access denied: ${user || 'anonymous'} ${mode} ${resourceUrl} — ${denied}`)
const status = user ? 403 : 401
throw new HttpError(status, denied)
}
}
async getPermissions (user, resourceUrl, isContainer) {
const { aclUrl, aclBody, isDefault, directory } = await this.findACL(resourceUrl, isContainer)
if (!aclBody) return { user: [], public: [] }
const graph = $rdf.graph()
try {
$rdf.parse(aclBody, graph, aclUrl, 'text/turtle')
} catch {
return { user: [], public: [] }
}
const doc = $rdf.sym(resourceUrl)
const aclDoc = $rdf.sym(aclUrl)
const dir = isDefault ? $rdf.sym(directory) : null
const modes = ['Read', 'Write', 'Append', 'Control']
const userPerms = []
const publicPerms = []
for (const mode of modes) {
const agent = user ? $rdf.sym(user) : null
if (agent) {
const denied = aclCheck.accessDenied(graph, doc, dir, aclDoc, agent, [ACL(mode)], null, null)
if (!denied) userPerms.push(mode.toLowerCase())
}
const pubDenied = aclCheck.accessDenied(graph, doc, dir, aclDoc, null, [ACL(mode)], null, null)
if (!pubDenied) publicPerms.push(mode.toLowerCase())
}
return { user: userPerms, public: publicPerms }
}
async findACL (resourceUrl, isContainer) {
let url = resourceUrl
let isDefault = false
// Walk up the hierarchy looking for .acl files
for (let depth = 0; depth < 30; depth++) {
const aclUrl = url.endsWith('/') ? url + '.acl' : url + '.acl'
const aclPathname = aclUrl.slice(this.rootUrl.length)
try {
const filePath = this.ldp.resolve(aclPathname)
const body = await fs.readFile(filePath, 'utf8')
return {
aclUrl,
aclBody: body,
isDefault,
directory: isDefault ? url : null
}
} catch {
// No ACL here, go up
}
if (url === this.rootUrl || url === this.rootUrl + '/') {
break
}
// Move to parent
isDefault = true
if (url.endsWith('/')) {
url = url.slice(0, -1)
}
const lastSlash = url.lastIndexOf('/')
if (lastSlash <= url.indexOf('/', 8)) {
url = this.rootUrl + '/'
} else {
url = url.slice(0, lastSlash + 1)
}
}
return { aclUrl: null, aclBody: null, isDefault: false, directory: null }
}
}
export function wacAllowHeader (perms) {
const u = perms.user.length ? perms.user.join(' ') : ''
const p = perms.public.length ? perms.public.join(' ') : ''
return `user="${u}",public="${p}"`
}