sails-permissions icon indicating copy to clipboard operation
sails-permissions copied to clipboard

row level security

Open phishy opened this issue 10 years ago • 25 comments

The README mentions row-level security. Is there any documentation for how this works?

phishy avatar Apr 25 '15 16:04 phishy

:+1:

maheshchari avatar May 15 '15 05:05 maheshchari

1f44d

jonasho avatar Jun 02 '15 13:06 jonasho

+1

dottodot avatar Jun 10 '15 13:06 dottodot

+1

phibya avatar Jun 14 '15 05:06 phibya

I see that it is not implemented yet, https://github.com/tjwebb/sails-permissions/blob/master/api/models/Permission.js#L27

satyadeepk avatar Jun 14 '15 23:06 satyadeepk

@phishy this is now supported as of #69. Any help testing it out is appreciated.

tjwebb avatar Jun 17 '15 07:06 tjwebb

I would be happy to test but do you have any info on how to use it?

dottodot avatar Jul 01 '15 11:07 dottodot

this is awesome!

zxshinxz avatar Jul 13 '15 18:07 zxshinxz

Ok just trying the permission criteria but it doesn't seem to have any effect.

I have set the permission using

PermissionService.grant({
    role: 'public',
    model: 'Product',
    action: 'read',
    criteria: {
      blacklist: ['inventory']
    }
  });

where inventory is an association to a model

I was hoping that any public user would then not see the inventory attribute which is populated in my controller.

I've also tried it on another field that isn't an association and the attribute is still displayed in the response.

dottodot avatar Jul 16 '15 10:07 dottodot

@dottodot Are you using a blueprint controller or a custom controller for the Product/read request? Blacklist filtering only works if the result is sent back with res.ok, so if you are using a custom controller, make sure you send the response via res.ok.

I have also noticed some unusual behavior with the 'public' role. I will see if I can reproduce this when I have a few minutes.

ryanwilliamquinn avatar Jul 16 '15 12:07 ryanwilliamquinn

Yes I'm using a custom controller, I've just changed it to res.ok and that's made no difference and I've tried on the registered role and it's still the same.

I'm wondering if the 'where' option is required for it to work but not sure what you'd use field that can be any value.

dottodot avatar Jul 16 '15 12:07 dottodot

No, the 'where' option should not be required for it to work. Let me have a look at it now.

ryanwilliamquinn avatar Jul 16 '15 13:07 ryanwilliamquinn

Have you checked that the Permission looks correct in sails console, via Permission.find()?

ryanwilliamquinn avatar Jul 16 '15 13:07 ryanwilliamquinn

OK below is the output of Permission.find() but I'm not sure what to look for to know if it's right.

{ _context: 
   { connections: { mongo_development: [Object] },
     waterline: 
      { _collections: [Object],
        _connections: {},
        collections: [Object],
        connections: [Object],
        schema: [Object] },
     adapter: 
      { connections: [Object],
        dictionary: [Object],
        query: [Circular],
        collection: 'permission',
        identity: 'permission' },
     _attributes: 
      { model: [Object],
        action: [Object],
        relation: [Object],
        role: [Object],
        user: [Object],
        criteria: [Object],
        id: [Object],
        createdAt: [Object],
        updatedAt: [Object] },
     defaults: 
      { migrate: 'safe',
        schema: true,
        connection: 'mongo_development' },
     _cast: { _types: [Object] },
     _schema: 
      { context: [Circular],
        schema: [Object],
        hasSchema: true },
     _validator: 
      { validations: [Object],
        reservedProperties: [Object] },
     _callbacks: 
      { beforeValidate: [Object],
        afterValidate: [Object],
        beforeUpdate: [Object],
        afterUpdate: [Object],
        beforeCreate: [Object],
        afterCreate: [Object],
        beforeDestroy: [Object],
        afterDestroy: [Object] },
     _instanceMethods: {},
     hasSchema: true,
     migrate: 'safe',
     _model: [Function: bound],
     primaryKey: 'id',
     _transformer: { _transformations: {} },
     adapterDictionary: 
      { pkFormat: 'mongo_development',
        syncable: 'mongo_development',
        defaults: 'mongo_development',
        registerConnection: 'mongo_development',
        teardown: 'mongo_development',
        describe: 'mongo_development',
        define: 'mongo_development',
        drop: 'mongo_development',
        native: 'mongo_development',
        mongo: 'mongo_development',
        create: 'mongo_development',
        createEach: 'mongo_development',
        find: 'mongo_development',
        update: 'mongo_development',
        destroy: 'mongo_development',
        count: 'mongo_development',
        join: 'mongo_development',
        stream: 'mongo_development',
        identity: 'mongo_development' },
     pkFormat: 'string',
     syncable: true,
     registerConnection: [Function: bound],
     teardown: [Function: bound],
     define: [Function: bound],
     native: [Function: bound],
     mongo: { objectId: [Function] },
     findOneByAction: [Function: bound],
     findOneByActionIn: [Function: bound],
     findOneByActionLike: [Function: bound],
     findByAction: [Function: bound],
     findByActionIn: [Function: bound],
     findByActionLike: [Function: bound],
     countByAction: [Function: bound],
     countByActionIn: [Function: bound],
     countByActionLike: [Function: bound],
     actionStartsWith: [Function: bound],
     actionContains: [Function: bound],
     actionEndsWith: [Function: bound],
     findOneByRelation: [Function: bound],
     findOneByRelationIn: [Function: bound],
     findOneByRelationLike: [Function: bound],
     findByRelation: [Function: bound],
     findByRelationIn: [Function: bound],
     findByRelationLike: [Function: bound],
     countByRelation: [Function: bound],
     countByRelationIn: [Function: bound],
     countByRelationLike: [Function: bound],
     relationStartsWith: [Function: bound],
     relationContains: [Function: bound],
     relationEndsWith: [Function: bound],
     findOneById: [Function: bound],
     findOneByIdIn: [Function: bound],
     findOneByIdLike: [Function: bound],
     findById: [Function: bound],
     findByIdIn: [Function: bound],
     findByIdLike: [Function: bound],
     countById: [Function: bound],
     countByIdIn: [Function: bound],
     countByIdLike: [Function: bound],
     idStartsWith: [Function: bound],
     idContains: [Function: bound],
     idEndsWith: [Function: bound],
     findOneByCreatedAt: [Function: bound],
     findOneByCreatedAtIn: [Function: bound],
     findOneByCreatedAtLike: [Function: bound],
     findByCreatedAt: [Function: bound],
     findByCreatedAtIn: [Function: bound],
     findByCreatedAtLike: [Function: bound],
     countByCreatedAt: [Function: bound],
     countByCreatedAtIn: [Function: bound],
     countByCreatedAtLike: [Function: bound],
     createdAtStartsWith: [Function: bound],
     createdAtContains: [Function: bound],
     createdAtEndsWith: [Function: bound],
     findOneByUpdatedAt: [Function: bound],
     findOneByUpdatedAtIn: [Function: bound],
     findOneByUpdatedAtLike: [Function: bound],
     findByUpdatedAt: [Function: bound],
     findByUpdatedAtIn: [Function: bound],
     findByUpdatedAtLike: [Function: bound],
     countByUpdatedAt: [Function: bound],
     countByUpdatedAtIn: [Function: bound],
     countByUpdatedAtLike: [Function: bound],
     updatedAtStartsWith: [Function: bound],
     updatedAtContains: [Function: bound],
     updatedAtEndsWith: [Function: bound],
     definition: 
      { model: [Object],
        action: [Object],
        relation: [Object],
        role: [Object],
        user: [Object],
        id: [Object],
        createdAt: [Object],
        updatedAt: [Object] },
     meta: { junctionTable: false },
     alter: [Function: bound],
     buildDynamicFinders: [Function: bound],
     constructor: [Function: bound],
     contains: [Function: bound],
     count: [Function: bound],
     create: [Function: bound],
     createEach: [Function: bound],
     describe: [Function: bound],
     destroy: [Function: bound],
     drop: [Function: bound],
     endsWith: [Function: bound],
     find: [Function: bound],
     findAll: [Function: bound],
     findLike: [Function: bound],
     findOne: [Function: bound],
     findOneLike: [Function: bound],
     findOrCreate: [Function: bound],
     findOrCreateEach: [Function: bound],
     generateAssociationFinders: [Function: bound],
     generateDynamicFinder: [Function: bound],
     join: [Function: bound],
     select: [Function: bound],
     startsWith: [Function: bound],
     stream: [Function: bound],
     sync: [Function: bound],
     update: [Function: bound],
     validate: [Function: bound],
     where: [Function: bound],
     associations: 
      [ [Object],
        [Object],
        [Object],
        [Object] ],
     broadcast: [Function],
     getAllContexts: [Function],
     message: [Function],
     publish: [Function: bound],
     pluralize: [Function],
     room: [Function: bound],
     classRoom: [Function],
     _classRoom: [Function],
     subscribers: [Function],
     watchers: [Function],
     subscribe: [Function: bound],
     unsubscribe: [Function: bound],
     publishUpdate: [Function: bound],
     publishDestroy: [Function: bound],
     publishAdd: [Function: bound],
     publishRemove: [Function: bound],
     publishCreate: [Function: bound],
     watch: [Function: bound],
     unwatch: [Function: bound],
     introduce: [Function: bound],
     retire: [Function: bound],
     autosubscribe: true },
  _method: [Function: bound],
  _criteria: { where: null },
  _values: null,
  _deferred: null }

dottodot avatar Jul 16 '15 13:07 dottodot

That method returns a promise, which is what you are seeing there. Try this: Role.find({name: 'public'}).exec(console.log) Take the id from the role and plug it into this query: Permission.find({role: theIdFromRole, action: 'read'}).exec(console.log)

ryanwilliamquinn avatar Jul 16 '15 13:07 ryanwilliamquinn

Actually the second query should look like this: Permission.find({role: theIdFromRole, action: 'read'}).populate('criteria').exec(console.log)

ryanwilliamquinn avatar Jul 16 '15 13:07 ryanwilliamquinn

So this is the response...

[
    {
        criteria: [
            {
                blacklist: [
                    'inventory'
                ],
                permission: '55a7b4efeed630c81a14e659',
                createdAt: '2015-07-16T13: 43: 11.118Z',
                updatedAt: '2015-07-16T13: 43: 11.118Z',
                id: '55a7b4efeed630c81a14e65a'
            }
        ],
        model: '55a7b4c0a2e56fae1a48666b',
        role: '55a7b4c0a2e56fae1a486679',
        action: 'read',
        relation: 'role',
        createdAt: '2015-07-16T13: 43: 11.111Z',
        updatedAt: '2015-07-16T13: 43: 11.114Z',
        id: '55a7b4efeed630c81a14e659'
    }
]

dottodot avatar Jul 16 '15 13:07 dottodot

That looks good. When you tried on the registered role, did you make the request with a registered (logged in) user?

ryanwilliamquinn avatar Jul 16 '15 14:07 ryanwilliamquinn

Yes I logged in as the registered user then made the request.

But just found the cause. It was a combination of me missing adding the Criteria Policy to policy.js (sorry about that) and then I was also sending my response as below for pagination purposes.

res.ok({
            items: products,
            totalRecords: count
          });

It would seem the criteria policy will only work if sent as

res.ok(products)

Is there any way around this? Or is it just a case of me creating a custom Criteria Policy to meet my needs?

dottodot avatar Jul 16 '15 15:07 dottodot

Sorry I didn't respond to this earlier. You are correct, the 'read' filtering only works if you send it like res.ok(products) The code that does it is here: https://github.com/tjwebb/sails-permissions/blob/master/api/policies/CriteriaPolicy.js#L84

I will keep thinking about how to extend the read attribute filtering.

ryanwilliamquinn avatar Jul 28 '15 01:07 ryanwilliamquinn

@phishy there is a bit of documentation at the bottom of this page about row-level security: https://github.com/tjwebb/sails-permissions/wiki/Managing-Roles-and-Permissions

ryanwilliamquinn avatar Jul 28 '15 01:07 ryanwilliamquinn

@dottodot Did you ever manage to find a solution for this? I'm having the same problem.

khchan avatar Dec 08 '15 21:12 khchan

@khchan Sorry which problem are you referring as I mentioned a couple I was having.

dottodot avatar Dec 09 '15 09:12 dottodot

@dottodot ah should have been more specific. I was wondering if you found a solution for res.ok to have a pagination total or did you end up having to create a custom criteria policy?

khchan avatar Dec 09 '15 12:12 khchan

@khchan I did it by sending the count in a header like this.

User.count().then(function(count) {
      User.find().paginate({
        page: req.param('page'),
        limit: req.param('limit')
      }).then(function(users) {
        res.set('Access-Control-Expose-Headers', 'X-Total-Count');
        res.set('X-Total-Count', count);
        res.ok(users);
      });
    });

dottodot avatar Dec 09 '15 12:12 dottodot