Repository navigation
Conversation
43ec1f7 to
54a227d
Compare
- Implement agentGroup parsing and serializing - Implement group.initFromGraph(), add tests - Add checkAccess() support with loading group listings
929637d to
b1ce882
Compare
6a51d95 to
fdac218
Compare
Fixes issue #12.
| let target = null | ||
| let base = null | ||
| let base = this.aclUrl | ||
| return new Promise((resolve, reject) => { |
| "babel-cli": "^6.14.0", | ||
| "babel-preset-es2015": "^6.14.0", | ||
| "rdflib": "^0.12.1", | ||
| "rdflib": "^0.13.0", |
| var ns = vocab(rdf) | ||
| var statements = [ | ||
| let statement | ||
| let fragment = rdf.namedNode('#' + this.hashFragment()) |
There was a problem hiding this comment.
Shouldn't NamedNode use full IRI instead of just fragment? Just as PermissionSet constructor accepts aclUrl Authorization should also accept aclUrl and use it to construct NamedNode with full IRI. I mentioned in the chatroom that currently .toNT() will produce invalid N-Triples
There was a problem hiding this comment.
this commit based on branch from this PR implements that elf-pavlik@e439300
There was a problem hiding this comment.
If single Authorization doesn't have to represent itself as RDF graph, possibly code from Authorization#rdfStatements() could move to PermissionSet#buildGraph() which would not require passing aclUrl to constructor of Authorization. It seems that currently original IRIs of authorizations from parsed ACL document will not get preserved and Authorization#rdfStatements() will always generate new IRI for each Authorization. Plus Authorization#mergeWith suggests that it doesn't really exactly represent instances of acl:Authorization (not "for a single resource and for a single webId (either agent or group)"). Actually it seems that PermissionSet should have responsibility to create RDF instances of acl:Authorization just as PermissionSet#initFromGraph() converts them into solid-permissions.js Authorization objects (!= acl:Authorization)
There was a problem hiding this comment.
I captured above in dedicated issue #17 since this PR doesn't really introduce that problem and it can get addressed independently after merning this PR.
There was a problem hiding this comment.
Good point. I'll throw in my $.02 that the aclUrl could alternatively be passed into the rdfStatements method so that an authorization can be reserialized for different graphs.
nicola
left a comment
There was a problem hiding this comment.
Looks great to me, this has still the group issue, right?
|
@nicola Yeah, this does still have the group issue. But, as I point out in the discussion at https://github.com/solid/web-access-control-spec#infinite-request-loops-in-group-listings -- this is an acceptable risk, and at worst results in a few extra requests on the server. We can address it later if it becomes a major problem. |
|
hey @dmitrizagidulin although it is a great idea to mention, Doing the call just once, would basically a few (less than hundred?) times might crash the server since you will end up running out of memory. As soon as one finds a publicly writable space, they can attack the server by creating the files tha will cause the infinite loop. I recall that the simples solution was that the server, when updating an ACL, will make sure that the new updated ACL does not reference a group where the acl to access that group is the ACL itself. I think this check might fix the issue. Before this trick is in place, we should not add this to release. We can add it, but disabling external fetching of groups |
|
@nicola Sure, we can discuss the various strategies of mitigating the group fetch loop. This is not the PR to be concerned about, however -- there will be a corresponding PR to |
dan-f
left a comment
There was a problem hiding this comment.
Group permissions are exciting! I've got a bunch of thoughts on this PR.
| var ns = vocab(rdf) | ||
| var statements = [ | ||
| let statement | ||
| let fragment = rdf.namedNode('#' + this.hashFragment()) |
There was a problem hiding this comment.
Good point. I'll throw in my $.02 that the aclUrl could alternatively be passed into the rdfStatements method so that an authorization can be reserialized for different graphs.
| ns.acl('Authorization')) | ||
| ] | ||
| if (this.agent) { | ||
| if (this.isAgent()) { |
There was a problem hiding this comment.
To nitpick, i'm finding the is* methods (isPublic, isGroup, isAgent) to be a bit confusing. An authorization isn't a group nor is it an agent, but an authorization. Maybe something like isFor* instead?
| * @method rdfStatements | ||
| * @return {Array<Statement>} List of RDF statements representing this Auth, | ||
| * @param rdf {RDF} RDF Library | ||
| * @return {Array<Triple>} List of RDF statements representing this Auth, |
There was a problem hiding this comment.
This has nothing to do with this PR, but I really wish we had an RDF library that could pass around immutable indexed graphs and merge/intersect/union etc. them. Arrays of statements feels a bit awkward.
| /** | ||
| * Sets the agent WebID for this authorization. Implemented as `setAgent()` | ||
| * setter method to enforce mutual exclusivity with `group` property, until | ||
| * ES6 setter methods become available. |
There was a problem hiding this comment.
ES6 setter methods are available.
There was a problem hiding this comment.
Another thing is to detect the type of agent (foaf:Agent or vcard:Group) and choose the appropriate predicate.
| constructor (options = {}) { | ||
| this.uri = options.uri | ||
| this.uid = options.uid | ||
| this.members = options.members || {} |
There was a problem hiding this comment.
There was a problem hiding this comment.
Set would not be appropriate here -- the members object is used as a dictionary, for key-based lookups. (Using Set would require using .find() to achieve equivalent functionality.)
There was a problem hiding this comment.
Your use case is exactly what sets are for. O(1) membership lookups.
There was a problem hiding this comment.
And that way you don't have to map webIds to booleans. The webId is either in the set or it isn't.
There was a problem hiding this comment.
I think you're thinking of Maps or something. Sets do not have O(1) lookups.
There was a problem hiding this comment.
Are you referring to the has()? Are those operations O(1)?
There was a problem hiding this comment.
Membership checks are the sole use case for a set. They're implemented in almost every language using a hash table.
There was a problem hiding this comment.
Ah, my bad, I didn't realize that. Sure, I can change that part to use Sets instead.
| * @param agent {String|Quad} Agent URL (or `acl:agent` RDF triple). | ||
| * @param agent {string|Quad|GroupListing} Agent URL (or `acl:agent` RDF triple). | ||
| */ | ||
| setAgent (agent) { |
There was a problem hiding this comment.
I'm concerned that these setAgent/setGroup methods are adding a fair bit of complexity for not much payoff. Why should permissions be mutable? Can't we handle the modification of an ACL at the PermissionSet level?
There was a problem hiding this comment.
Another way to put this is that the current rules for mutating an Authorization's agent/group is difficult to reason about. I think that part of the problem is that at a conceptual level, mutating a single authorization is a semantically broad operation. An Authorization is currently a tuple of modes, agent/group, allowed origins, etc. With the current set of setter methods you can modify an Authorization to the point that it's not remotely the same authorization anymore, e.g. by changing the access modes and the agent/group.
There was a problem hiding this comment.
However if you wish to stick with this approach I'd like to see the agent be abstracted away from the concerns of this method. Could we define an Agent class/superclass which represents either a group, individual agent, or "everyone"? That way we'd only need a single setAgent method, and the concern of whether the agent is an individual, group, or everyone could be handled during serialization using a duck typing approach.
| const vocab = require('solid-namespace') | ||
|
|
||
| /** | ||
| * Ad Hoc ACL Group Listing |
There was a problem hiding this comment.
What about group permissions is ad hoc?
| let result = false | ||
| let membershipMatches = this.groupsForMember(agentId) | ||
| membershipMatches.forEach(groupWebId => { | ||
| if (this.checkAccessForAgent(resourceUrl, groupWebId, accessMode)) { |
There was a problem hiding this comment.
This could be more efficient using .find().
|
|
||
| /** | ||
| * Returns a list of webIds of groups to which this agent belongs. | ||
| * Note: Only checks loaded groups (assumes a previous `loadGroups()` call). |
There was a problem hiding this comment.
You know what they say about assumptions...
There was a problem hiding this comment.
I think the loading of groups should be transparent to the caller. It should not be the responsibility of the client to handle/understand the internal implementation of the library.
| let fetchGraph = options.fetchGraph | ||
| let rdf = options.rdf || this.rdf | ||
| if (!fetchGraph) { | ||
| return Promise.reject(new Error('Cannot load groups, fetchGraph() not supplied')) |
There was a problem hiding this comment.
I'm not a fan of this practice of passing fetchGraph / rdf / webclient objects around to methods which need them. At the very, very least there should be a default implementation that's provided by the library.
|
It looks like two small fixes needed for #16 got stuck in this PR which seems not active for almost a month now :( |
|
@elf-pavlik indeed. I'll see if I can add those two fixes in a separate PR. |
See https://github.com/solid/web-access-control-spec#groups-of-agents for specs
The usage is mostly unchanged; the main difference is that now the consuming code should pass a
fetchGraph()function in theoptionsparameter ofps.checkAccess(), so that it can load (local or remote) group listings.