balderdashy / sails

Realtime MVC Framework for Node.js
https://sailsjs.com
MIT License
22.84k stars 1.95k forks source link

Invalid policy setting for `/api/dogs`: `inRole("dogs")` does not correspond to any of the loaded policies. #6901

Open intervalia opened 4 years ago

intervalia commented 4 years ago

Node version: 10.15.- sails: ^1.2.3, sails-hook-orm: ^2.1.1, sails-hook-sockets: ^2.0.0, sails-mysql: ^1.0.1 Development environment


Any clue about this Error:

Invalid policy setting for `/api/dogs`: `inRole("dogs")` does not correspond to any of the loaded policies.

I have a file /api/policies/inRole.js that looks like this:

module.exports = (role) => (req, res, next) => {
  if (req.user && req.user.inRole(role)) {
    next();
  }

  res.status(403).json({message: `No access to the endpoint ${req.path}` });
};

And in the file config/policies.js I have this:

module.exports.policies = {
  '/api/dogs': 'inRole("dogs")'
};

I don't understand what I am doing wrong that would produce this error.

sailsbot commented 4 years ago

@intervalia Thanks for posting! We'll take a look as soon as possible.

In the mean time, there are a few ways you can help speed things along:

Please remember: never post in a public forum if you believe you've found a genuine security vulnerability. Instead, disclose it responsibly.

For help with questions about Sails, click here.

alxndrsn commented 4 years ago

@intervalia AFAIK there is no support for expressions when setting policies for controllers/routes. So instead of:

'/api/dogs': 'inRole("dogs")'

you'd need to define a separate policy, e.g. /api/policies/inDogsRole.js:

module.exports = (req, res, next) => {
  if (req.user && req.user.inRole('dogs')) {
    next();
  }

  res.status(403).json({message: `No access to the endpoint ${req.path}` });
};

and reference it like so:

'/api/dogs': 'inDogsRole'

Obviously you could create a shared library for the generic inRole(role), but still a little fiddly.

intervalia commented 4 years ago

I understand your way of doing things but we have several endpoints that can take one of several roles. So we need to be able to define these roles in a group (Array) And your current system makes that difficult.

We need it to "pass" if the user has ANY of the roles specified. And trying to create a function for every combination is unreasonable.

What I would really like is the ability to do this:

In the file file /api/policies/inRole.js:

module.exports = (roles) => {
  if (!Array.isArray(roles)) {
    roles = [roles];
  }

  return (req, res, next) => {
    if (req.user && req.user.inRole(roles)) {
      next();
    }

    res.status(403).json({message: `No access to the endpoint ${req.path}` });
  };
}

And in the file config/policies.js:

module.exports.policies = {
  '/api/dogs': 'inRole(["dogs","cats","birds"])'
};
alxndrsn commented 4 years ago

I agree this would be a useful improvement to how sails handles policies.

Looking at the source code at https://github.com/balderdashy/sails/blob/master/lib/hooks/policies/index.js, you might achieve what you want by assigning functions directly in the policy mapping, e.g.

module.exports.policies = {
  'mycontroller/action': (req, res, next) =>
      hasAnyRole(req, res, next, 'dog', 'cat', 'bird'),
  ...
};

function hasAnyRole(.. // implement role lookup and confirmation as appropriate

These functions are almost certainly ExpressJS middleware (https://expressjs.com/en/guide/writing-middleware.html).

You could then clean up the policy definitions by generating the policy functions themselves:

module.exports.policies = {
  'mycontroller/action': hasAnyRole('dog', 'cat', 'bird'),
  ...
};

function hasAnyRole(...roles) {
  return (req, res, next) => _hasAnyRole(req, res, next, ...roles),
}

function _hasAnyRole(.. // implement role lookup and confirmation as appropriate

I've made an example project at https://github.com/alxndrsn/sails-function-based-policies, so you can see what this might look like. Policies are defined in https://github.com/alxndrsn/sails-function-based-policies/blob/master/config/policies.js. Note that this example uses client-provided headers in place of roles retrieved on the server-side.

Reading https://sailsjs.com/documentation/concepts/policies, applying functions as policies like this doesn't seem to be a documented feature. OTOH this is a reasonably safe bet, as according to the commit history it was implemented in 2016. Maybe one of the sails team can tell us how dependable this feature is for future sails versions.

whichking commented 4 years ago

Hey, @intervalia!

@alxndrsn is correct in saying that policies don't currently allow the passing in of dynamic data to be evaluated and verified. When we have a situation like yours, we either create multiple policies to cover our various permission cases, or—where permissions vary a great deal—we handle permissions verification in the action itself.

As for assigning functions in the policy mapping, since this isn't a documented feature, we can't make any guarantees regarding its longevity as a solution.

intervalia commented 1 year ago

FYI: I am no longer on this project and have no way of validating if this is working or not. I still think it would be a good feature. Others will have to validate if it works.

eashaw commented 1 year ago

For anyone coming to this issue, you may want to take a look at https://github.com/luislobo/sails-hook-rbac.