Skip to content
This repository was archived by the owner on Sep 25, 2019. It is now read-only.

Implement Ad Hoc Group listing support (acl:agentGroup) - #15

Merged
kjetilk merged 3 commits into
masterfrom
dz_groups
Jul 13, 2018
Merged

kjetilk merged 3 commits into
masterfrom
dz_groups

Conversation

@dmitrizagidulin

@dmitrizagidulin dmitrizagidulin commented Jan 5, 2017 •

Copy link
Copy Markdown
Contributor

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 the options parameter of ps.checkAccess(), so that it can load (local or remote) group listings.

@dmitrizagidulin
dmitrizagidulin force-pushed the dz_groups branch 2 times, most recently from 43ec1f7 to 54a227d Compare January 5, 2017 22:47
- Implement agentGroup parsing and serializing
- Implement group.initFromGraph(), add tests
- Add checkAccess() support with loading group listings
@dmitrizagidulin

Copy link
Copy Markdown
Contributor Author

@dan-f / @timbl / @nicola - ready for review.

Comment thread src/permission-set.js
let target = null
let base = null
let base = this.aclUrl
return new Promise((resolve, reject) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should solve 2/2 in #16

Comment thread package.json
"babel-cli": "^6.14.0",
"babel-preset-es2015": "^6.14.0",
"rdflib": "^0.12.1",
"rdflib": "^0.13.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should solve 1/2 in #16

Comment thread src/authorization.js
var ns = vocab(rdf)
var statements = [
let statement
let fragment = rdf.namedNode('#' + this.hashFragment())

@elf-pavlik elf-pavlik Jan 8, 2017 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit based on branch from this PR implements that elf-pavlik@e439300

@elf-pavlik elf-pavlik Jan 8, 2017 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 nicola left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me, this has still the group issue, right?

@dmitrizagidulin

Copy link
Copy Markdown
Contributor Author

@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.

@nicola

nicola commented Jan 9, 2017 •

Copy link
Copy Markdown

hey @dmitrizagidulin although it is a great idea to mention,
I think would create an infinite loop of requests that might never end.

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

@dmitrizagidulin

Copy link
Copy Markdown
Contributor Author

@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 node-solid-server that will be using this group support, so we can discuss the strategies there.

@dan-f dan-f left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Group permissions are exciting! I've got a bunch of thoughts on this PR.

Comment thread src/authorization.js
var ns = vocab(rdf)
var statements = [
let statement
let fragment = rdf.namedNode('#' + this.hashFragment())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/authorization.js
ns.acl('Authorization'))
]
if (this.agent) {
if (this.isAgent()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread src/authorization.js
* @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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/authorization.js
/**
* 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ES6 setter methods are available.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing is to detect the type of agent (foaf:Agent or vcard:Group) and choose the appropriate predicate.

Comment thread src/group-listing.js
constructor (options = {}) {
this.uri = options.uri
this.uid = options.uid
this.members = options.members || {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your use case is exactly what sets are for. O(1) membership lookups.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And that way you don't have to map webIds to booleans. The webId is either in the set or it isn't.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're thinking of Maps or something. Sets do not have O(1) lookups.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you referring to the has()? Are those operations O(1)?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Membership checks are the sole use case for a set. They're implemented in almost every language using a hash table.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, my bad, I didn't realize that. Sure, I can change that part to use Sets instead.

Comment thread src/authorization.js
* @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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/group-listing.js
const vocab = require('solid-namespace')

/**
* Ad Hoc ACL Group Listing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about group permissions is ad hoc?

Comment thread src/permission-set.js
let result = false
let membershipMatches = this.groupsForMember(agentId)
membershipMatches.forEach(groupWebId => {
if (this.checkAccessForAgent(resourceUrl, groupWebId, accessMode)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be more efficient using .find().

Comment thread src/permission-set.js

/**
* Returns a list of webIds of groups to which this agent belongs.
* Note: Only checks loaded groups (assumes a previous `loadGroups()` call).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know what they say about assumptions...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/permission-set.js
let fetchGraph = options.fetchGraph
let rdf = options.rdf || this.rdf
if (!fetchGraph) {
return Promise.reject(new Error('Cannot load groups, fetchGraph() not supplied'))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@elf-pavlik

Copy link
Copy Markdown

It looks like two small fixes needed for #16 got stuck in this PR which seems not active for almost a month now :(

@dmitrizagidulin

Copy link
Copy Markdown
Contributor Author

@elf-pavlik indeed. I'll see if I can add those two fixes in a separate PR.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants