Sails.js Policies, is there an OR operator to allow an action if one of a group of policies succeeds?

StackOverflow https://stackoverflow.com/questions/18876057

  •  29-06-2022
  •  | 
  •  

سؤال

When configuring policies in sails in config/policies.js such as:

    ActivityController: {
        create: ['authenticated'],
        update: ['authenticated', 'isActivityOwner'],
        destroy: ['authenticated' ,'isActivityOwner']
    }

Is there any functionality that would allow me to grant access to the action provided one or more of a group of policies succeeds maybe something like:

    ActivityController: {
        create: ['authenticated'],
        update: ['authenticated', {or:['isActivityOwner', 'isAdmin']}],
        destroy: ['authenticated' ,'isActivityOwner']
    }

Alternatively is it possible to create composite policies so that in one policy I may check one or more other policies?

If both of these options seem like poor solutions, can you suggest an approach that would would be considered better practice?

Forgive me if this is a bit obvious but I'm fairly new to sails and node in general, and thanks in advance for any help!

هل كانت مفيدة؟

المحلول

I haven't found any official support for operators in sails policies but here is what I am doing.

ActivityController: {
    update: ['authenticated', 'orActivityOwner', 'orAdmin', orPolicy],
}

Both orActivityOwner and orAdmin return next() as if they are valid. But they also set a boolean value to a session variable. Remember, policies are executed from left to right. I added an orPolicy to the end which will then evaluate the state of our session variable.

نصائح أخرى

check out sails-must:

ActivityController: {
    create: 'authenticated',
    update: ['authenticated', must().be.the.owner.or.be.a.member.of('admins')],
    destroy: ['authenticated', must().be.the.owner]
}

I've created a sails hook to be able to add parameters to policies:
https://github.com/mastilver/sails-hook-parametized-policies

I've setup an example where I defined an or policy:

module.exports = function(firstPolicy, secondPolicy){

    return function(req, res, next){


        var fakeRes = {};

        for(var i in res){
            if(i === 'forbidden'){
                // override the functions you want the `or` factory to handle
                fakeRes[i] = function(){
                    secondPolicy(req, res, next);
                };
            }
            else{
                fakeRes[i] = res[i];
            }
        }


        firstPolicy(req, fakeRes, next);
    }
}

Which you can use that way:

ActivityController: {
        create: ['authenticated'],
        update: ['authenticated', 'or(isActivityOwner, isAdmin)'],
        destroy: ['authenticated' ,'isActivityOwner']
    }

Just to complete the previous answer, that works like a charm :


Piece of information

But they also set a boolean value to a session variable

I myself prefer setting this boolean to the req object, that :

  • Is more semantic (access granted or not to ressource for the request, not for entire session)
  • Does not requires me to manually reset this variable (I should add that, if you DO want to use session like in @Travis solution , the last orPolicy policy must reset (even unset) the variable in order to protect the next request)

My implementation

config/policies.js :

MyController: {
  find: ['orIsTest1', 'orIsTest2', 'protectedResourceGranted']
}

api/policies/orIsTest1.js :

module.exports = function(req, res, next) {
  req.protectedResourceGranted = req.protectedResourceGranted || WHATEVERFIRSTTEST;

  return next();
};

api/policies/orIsTest2.js

module.exports = function(req, res, next) {
  req.protectedResourceGranted = req.protectedResourceGranted || WHATEVERSECONDTEST;

  return next();
};

api/policies/protectedResourceGranted.js

module.exports = function(req, res, next) {
  if(req.protectedResourceGranted) {
    return next();
  }

  return res.forbidden();
};

NB: Just answering 'cause I haven't got enough reputation to comment.

The other answers here work great, but here is an implementation that I find slightly cleaner.

Instead of creating policies designed for an OR situation that call next() even though they should fail, you can modify your existing policies to use in an AND/OR context, while hardly changing their behavior. Then create a composite policy (like the OP suggested) that checks the modified existing policies.

config/policies.js with example controllers and policies:

AdminController: {
  adminDashboard: 'isAuthenticated',
},

ItemController: {
  findOne: 'isPublishedOrIsAuthenticated'
}

api/policies/isAuthenticated.js and api/policies/isPublished.js and any other policy you want to use as a part of an AND/OR check:

If next was set to the true boolean (as opposed to a callback), just return true or false before the policy would normally return next(), res.notFound(), etc.

module.exports = function(req, res, next) {
  // do some checking
  if(next === true) return true; // or return false
  return next();
};

Note that we need to use the triple-equals sign here.

api/policies/isPublishedOrIsAuthenticated.js

module.exports = function(req, res, next) {
  var isPublished = require('./isPublished.js');
  var isAuthenticated = require('./isAuthenticated.js');

  // This reads like what we are trying to achieve!
  // The third argument in each call tells the function to return a boolean
  if(isPublished(req, res, true) || isAuthenticated(req, res, true))
    return next();

  return res.notFound();
};
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top