accesscontrol icon indicating copy to clipboard operation
accesscontrol copied to clipboard

Filtering inside an array of collections

Open davedc opened this issue 8 years ago • 6 comments

First of all, thanks for the amazing library. So far it has saved us a ton of time figuring out role and attribute based permissions ourselves. Really appreciate it.

A small hiccup we encountered is filtering inside collections.

Given this object:

{
  locality: 'A locality',
  street: 'A street',
  properties: [
    { 
      name: 'The Oasis',
      byLaws: 'The bylaws',
      occupants: [
        { name: 'Dan', age: 31 },
        { name: 'Roy', age: 22 }
      ]
    },
    { 
      name: 'Azure',
      byLaws: 'The bylaws',
      occupants: [
        { name: 'Annie', age: 32 },
        { name: 'Paul', age: 33 }
      ]
    }
  ]
}

I understand I can filter out street within the root with !street but how do we filter out byLaws within the properties? or how about the age within the occupants of a property?

So far I've tried !*.byLaws, !**.byLaws, !*.*.byLaws and !*.**.byLaws. For age I can imagine it would follow similar patterns for restricting byLaws. Any thoughts on how this can be done?

Thanks again

davedc avatar Oct 26 '17 23:10 davedc

Thanks.

AccessControl filters a given object or array of objects. It treats arrays in deeper levels as values (not sub-collections) bec. the typical use case is a model fetched from a database.

The data object in your example is more complex (i.e. model with sub-collections). To filter out byLaws property of that sub-collection, you'd do !properties[*].byLaws. Here, * stands for "at any index". However, this type of structure is not yet supported — but in the road map.

I'll mark this as a feature request but cannot guarantee it'll be included in the next version (which will probably be a patch release).

onury avatar Oct 27 '17 16:10 onury

@onury I'm keen to put together a pull request if you can point me in the right direction.

I see that this would be a change on the notation package rather than here.

davedc avatar Oct 29 '17 12:10 davedc

That'd be great but it'll be a bit tricky. Yes we need to update Notation. I've opened an issue here.

onury avatar Oct 29 '17 16:10 onury

supporting this feature. very useful.. thank you..

goodideal avatar Jan 22 '18 11:01 goodideal

@onury, hi! I need the similar feature. I need to filter a collection that is one of my model's property. But I need filter to be able to remove some items from the collection by items' values.

Please, consider the following structure:

{
  name: "John",
  lastName: "Doe",
  age: 23,
  files: [
    {
      name: '1.jpg',
      type: 'public'
    },
    {
      name: '2.jpg',
      type: 'private'
    },
    {
      name: '3.jpg',
      type: 'public'
    }
  ]
}

As I understand, you're discussing here the issue with filtering the files array. It is not possible to remove, for example, name property from the objects that are in the files array.

In my case, I need to filter this files array in another way: I'd like to remove all the objects that have the property type equals to private. It would be great to take this into account during developing v3.

I didn't find the opened issue that describes this case.

Thanks!

turakvlad avatar Mar 27 '18 12:03 turakvlad

... It is not possible to remove, for example, name property from the objects that are in the files array.

Actually there is a dirty workaround if you define a file (sub) resource.

// grant permissions
ac.grant('user')
    .read('person') // attrs: ['name', 'lastName', 'age', 'files']
    .read('file', ['*', '!name']);

// Check permissions and filter
let filteredData;
let permission = ac.can('user').read('person');
if (permission.granted) {
    filteredData = permission.filter(data);
    // if we still have .files property after first filter:
    if (filteredData.files) {
        // checking kind of a sub-resource
        permission = ac.can('user').read('file');
        if (permission.granted) filteredData.files = permission.filter(filteredData.files);
    }
}

This multi-filtered data will output:

{
    name: "John",
    lastName: "Doe",
    age: 23,
    files: [
        { type: 'public' },
        { type: 'private' },
        { type: 'public' }
    ]
}

In my case, I need to filter this files array in another way: I'd like to remove all the objects that have the property type equals to private.

@turakvlad filter uses a glob notation list to exclude/include properties. Each negated notation will be removed at that level. But what you want is to remove a parent object conditionally by its property. I think this would complicate things too much. It's kind of, like data validation and actually it is not what filter is for.

It seems your use case does not enforce any role permission checks for this. So you may want to handle this within your data models. e.g. if using a database; SELECT ... WHERE type <> 'private'.

onury avatar Mar 27 '18 18:03 onury