Skip to content

Commit 314f31e

Browse files
committed
making solid-acl rdflib compatible
1 parent 6fd7517 commit 314f31e

2 files changed

Lines changed: 236 additions & 16 deletions

File tree

lib/acl.js

Lines changed: 234 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,249 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
2121

2222
function match (graph, s, p, o) {
2323
var matches = graph.statementsMatching(s ? $rdf.sym(s) : undefined, $rdf.sym(p), $rdf.sym(o));
24-
console.log(matches)
25-
return matches.map(function (triple) {
26-
triple.subject.toString = function () {
27-
return triple.subject.uri
24+
return matches
25+
}
26+
27+
function ACL (opts) {
28+
var self = this
29+
opts = opts || {}
30+
if (opts.store && opts.store.graph && !opts.fetch) {
31+
self.fetch = opts.store.graph.bind(opts.store)
32+
}
33+
self.fetch = self.fetch || opts.fetch
34+
self.match = opts.match || match
35+
self.suffix = opts.suffix || '.acl'
36+
}
37+
38+
ACL.prototype.isAcl = function (resource) {
39+
return !!S(resource).endsWith(this.suffix)
40+
}
41+
42+
ACL.prototype.can = function (user, mode, resource, callback, options) {
43+
debug('Can ' + user + ' ' + mode + ' ' + resource + '?')
44+
var self = this
45+
var accessType = 'accessTo'
46+
var acls = possibleACLs(resource, self.suffix)
47+
options = options || {}
48+
49+
// If it is an ACL, only look for control this resource
50+
if (self.isAcl(resource)) {
51+
mode = 'Control'
52+
}
53+
54+
async.eachSeries(
55+
acls,
56+
// Looks for ACL, if found, looks for a rule
57+
function (acl, next) {
58+
debug('Check if acl exist: ' + acl)
59+
60+
// Let's see if there is a file..
61+
self.fetch(acl, function (err, graph) {
62+
if (err || !graph || graph.statements.length === 0) {
63+
// TODO
64+
// If no file is found and we want to Control,
65+
// we should not be able to do that!
66+
// Control is only to Read and Write the current file!
67+
// if (mode === 'Control') {
68+
// return next(new Error("You can't Control an unexisting file"))
69+
// }
70+
if (err) debug('Error: ' + err)
71+
accessType = 'defaultForNew'
72+
return next()
73+
}
74+
self.findRule(
75+
graph, // The ACL graph
76+
user, // The webId of the user
77+
mode, // Read/Write/Append
78+
resource, // The resource we want to access
79+
accessType, // accessTo or defaultForNew
80+
acl, // The current Acl file!
81+
function (err) {
82+
return next(!err || err)
83+
}, options)
84+
})
85+
},
86+
function (err) {
87+
if (err === false || err === null) {
88+
debug('No ACL resource found - access allowed')
89+
err = new Error('No Access Control Policy found')
90+
}
91+
92+
if (err === true) {
93+
debug('ACL policy found')
94+
err = null
95+
}
96+
97+
if (err) {
98+
debug('Error: ' + err.message)
99+
if (!user || user.length === 0) {
100+
debug('Authentication required')
101+
err.status = 401
102+
err.message = 'Access to ' + resource + ' requires authorization'
103+
} else {
104+
debug(mode + ' access denied for: ' + user)
105+
err.status = 403
106+
err.message = 'Access denied for ' + user
107+
}
108+
}
109+
110+
return callback(err)
111+
})
112+
}
113+
114+
ACL.prototype.findAgentClass = function (graph, user, mode, resource, acl, callback) {
115+
var self = this
116+
117+
// Agent class statement
118+
var agentClassStatements = self.match(
119+
graph,
120+
acl,
121+
'http://www.w3.org/ns/auth/acl#agentClass',
122+
undefined)
123+
124+
if (agentClassStatements.length === 0) {
125+
return callback(false)
126+
}
127+
128+
async.some(agentClassStatements, function (agentClassTriple, found) {
129+
// Check for FOAF groups
130+
debug('Found agentClass policy')
131+
if (agentClassTriple.object.uri === 'http://xmlns.com/foaf/0.1/Agent') {
132+
debug(mode + ' allowed access as FOAF agent')
133+
return found(true)
134+
}
135+
136+
return found(false)
137+
}, callback)
138+
}
139+
140+
ACL.prototype.findRule = function (graph, user, mode, resource, accessType, acl, callback, options) {
141+
var self = this
142+
143+
// TODO check if this is necessary
144+
if (graph.statements.length === 0) {
145+
debug('ACL ' + acl + ' is empty')
146+
return callback(new Error('No policy found'))
147+
}
148+
149+
debug('Found policies in ' + acl)
150+
151+
// Check for mode
152+
var statements = self.getMode(graph, mode)
153+
if (mode === 'Append') {
154+
statements = statements
155+
.concat(self.getMode(graph, 'Write'))
156+
}
157+
158+
async.some(
159+
statements,
160+
function (statement, done) {
161+
var statementSubject = statement.subject.uri
162+
163+
// Check for origin
164+
var matchOrigin = self.matchOrigin(graph, statementSubject, options.origin)
165+
if (!matchOrigin) {
166+
debug('The request does not match the origin')
167+
return done(false)
168+
}
169+
170+
// Check for accessTo/defaultForNew
171+
if (!self.isAcl(resource) || accessType === 'defaultForNew') {
172+
debug('Checking for accessType:' + accessType)
173+
var accesses = self.matchAccessType(graph, statementSubject, accessType, resource)
174+
if (accesses) {
175+
debug('Cannot find accessType ' + accessType)
176+
return done(false)
28177
}
29-
return triple
178+
}
179+
180+
// Check for Agent
181+
var agentStatements = self.match(
182+
graph,
183+
statementSubject,
184+
'http://www.w3.org/ns/auth/acl#agent',
185+
user)
186+
187+
if (agentStatements.length) {
188+
debug(mode + ' access allowed (as agent) for: ' + user)
189+
return done(true)
190+
}
191+
192+
debug('Inspect agentClass')
193+
// Check for AgentClass
194+
return self.findAgentClass(graph, user, mode, resource, statementSubject, done)
195+
},
196+
function (found) {
197+
if (!found) {
198+
return callback(new Error('Acl found but policy not found'))
199+
}
200+
return callback(null)
201+
})
202+
}
203+
204+
// TODO maybe these functions can be integrated in the code
205+
ACL.prototype.getMode = function getMode (graph, mode) {
206+
var self = this
207+
return self.match(
208+
graph,
209+
undefined,
210+
'http://www.w3.org/ns/auth/acl#mode',
211+
'http://www.w3.org/ns/auth/acl#' + mode)
212+
}
213+
214+
ACL.prototype.matchAccessType = function matchAccessType (graph, rule, accessType, uri) {
215+
var self = this
216+
var matches = self.match(
217+
graph,
218+
rule,
219+
'http://www.w3.org/ns/auth/acl#' + accessType,
220+
uri)
221+
222+
return matches.some(function(match) {
223+
return S(uri).beginsWith(match)
224+
})
225+
226+
}
227+
228+
ACL.prototype.matchOrigin = function getOrigins (graph, rule, origin) {
229+
var self = this
230+
var origins = self.match(
231+
graph,
232+
rule,
233+
'http://www.w3.org/ns/auth/acl#origin',
234+
undefined)
235+
236+
if (origins.length) {
237+
return origins.some(function (triple) {
238+
return triple.object.uri === origin
30239
})
240+
}
241+
242+
return true
243+
}
244+
245+
function possibleACLs (uri, suffix) {
246+
var first = S(uri).endsWith(suffix) ? uri : uri + '.acl'
247+
var urls = [first]
248+
var parsedUri = url.parse(uri)
249+
var baseUrl = (parsedUri.protocol ? parsedUri.protocol + '//' : '') + (parsedUri.host || '')
250+
if (baseUrl + '/' === uri) {
251+
return urls
252+
}
253+
254+
var times = parsedUri.pathname.split('/').length
255+
for (var i = 0; i < times - 1; i++) {
256+
uri = path.dirname(uri)
257+
urls.push(uri + (uri[uri.length - 1] === '/' ? '.acl' : '/.acl'))
258+
}
259+
return urls
31260
}
32261

33262
function fetchDocument (ldp, baseUri) {
34263
return function (uri, callback) {
35264
var graph = $rdf.graph();
36265
async.waterfall([
37266
function (cb) {
38-
// URL is remote
39-
if (!S(uri).startsWith(baseUri)) {
40-
// Fetch remote source
41-
var headers = { headers: { 'Accept': 'text/turtle'}};
42-
return request.get(uri, headers, function(err, response, body) {
43-
return cb(err, body);
44-
});
45-
}
46267
// URL is local
47268
var newPath = S(uri).chompLeft(baseUri).s;
48269
// TODO prettify this
@@ -58,7 +279,6 @@ function fetchDocument (ldp, baseUri) {
58279
console.log(err)
59280
return cb(err, graph);
60281
}
61-
graph.length = graph.statements.length
62282
return cb(null, graph);
63283
}
64284
], callback);

test/acl.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,11 @@ describe('ACL HTTP', function() {
135135
});
136136
})
137137
describe("with defaultForNew in parent path", function () {
138-
it("should allow creation of new containers", function(done) {
138+
it("should allow creation of new files", function(done) {
139139
var options = createOptions('/acl/write-acl/empty-acl/test-folder', 'user1');
140140
options.body = "";
141141
request.put(options, function(error, response, body) {
142-
assert.equal(response.statusCode, 200);
142+
assert.equal(response.statusCode, 201);
143143
done();
144144
});
145145
});

0 commit comments

Comments
 (0)