Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation: add SQL support via objection-authorize #486

Closed
JaneJeon opened this issue Apr 11, 2021 · 20 comments
Closed

Documentation: add SQL support via objection-authorize #486

JaneJeon opened this issue Apr 11, 2021 · 20 comments
Labels

Comments

@JaneJeon
Copy link

Hi! I am a daily user of casl and over the past few years I've been using casl along with objection-authorize (a library that I've also been building for the past few years) to integrate casl in both the frontend and the backend isomorphically, and perhaps more importantly, ACTUALLY use the casl ACLs to filter/check SQL queries!

I understand that #8 is asking for Sequelize support, but it doesn't lend itself to plugin support very neatly, and I'm a personal advocate of Objection.js (the SQL ORM of choice), and perhaps most importantly, it's ALREADY proven to be working (see https://github.com/JaneJeon/blink for an example, especially under policies/ and models/ directory).

https://github.com/JaneJeon/objection-authorize has a full battery of tests that have been expanding over the years, and at this point I feel that it's "battle-tested" enough to recommend to the general public. So I'd appreciate it if you could add objection-authorize as an adapter for people using SQL + CASL, under the "Official Packages" in the documentation.

My hope is that with my library, people who're looking for SQL support don't have to overlook CASL because of the lack of support for SQL ORMs (and vice versa).

Thanks!

@stalniy
Copy link
Owner

stalniy commented Apr 12, 2021

Hi @JaneJeon

Thanks for letting me know! This is awesome! However, I need to take a closer look to your library before I can recommend it on casl resources.

@JaneJeon
Copy link
Author

Yeah, feel free to read through the README and the tests, and if you're still not convinced, please do look into how blink uses it to achieve isomorphism!

@stalniy
Copy link
Owner

stalniy commented Apr 14, 2021

Sorry @JaneJeon when you have a little kid Open Source activities start to move a bit slower :). I definitely will take a look but it will take some time

@JaneJeon
Copy link
Author

JaneJeon commented May 10, 2021

@stalniy would you still be willing to mention objection-authorize (as an unofficial ORM plugin) for those of us who don’t want to use Prisma? I really feel like years of battle testing has made the plugin suitable for production. Thx :)

@stalniy
Copy link
Owner

stalniy commented May 10, 2021

I'll take a look at your plugin till the end of this week. I know you posted this almost a month ago but I'm following my priorities

@JaneJeon
Copy link
Author

@stalniy that’s fine, I just thought I’d poke you now since it seemed like you were active again on this repo recently

@stalniy
Copy link
Owner

stalniy commented May 10, 2021

OK, I took a look. And have few questions:

  1. As far as I can understand the code, permissions are checked in model hooks, right?
  2. How do you fetch accessible records from database? For example, I want to get all articles that I can read from the database

UPDATE: what I'm looking for is functionality similar to @casl/mongoose accessibleBy method. How authorize works in case of multiple records like this, like in this case - https://github.com/JaneJeon/blink/blob/master/routes/api/users.js#L8 , Does it fetches everything, iterates and check permissions?

From what I can tell it goes through all found items and return error if any of them is not accessible by permission management (from here), correct?

@JaneJeon
Copy link
Author

Oh wow, that was quick.

Ok, to start off, objection-authorize is pretty different from casl-prisma or casl-mongoose (I took a look at both of those) in that it uses the context already in the query to make the authorization decision (this is relevant to understanding the plugin in general).

For example, if you want to see whether a User can edit a particular Post, you would some kind of query like this:

const post = await Post.query().findById(x)
await post.$query().patch(jsonbody).authorize(req.user)

This is just a simple example, but in that query you have 1. the post, 2. the user, 3. the actual body to update (so the plugin can do ABAC for you), and objection-authorize takes those and make the authorization decision FOR you with the help of authorization libraries (currently, it is only casl but in the past it also supported accesscontrol library).

Now, to answer your questions:

  1. The permission is checked when you want to see if a particular query should be even allowed to run by attaching .authorize() to it. The core one half of the library is gathering context from your query (https://github.com/JaneJeon/objection-authorize/blob/master/src/index.js), and in particular, it relies on Objection’s “static” hooks (https://github.com/JaneJeon/objection-authorize/blob/master/src/index.js#L74) to check the permission of the query itself right before it executes: https://github.com/JaneJeon/objection-authorize/blob/master/src/adapters/base.js (note that the permission checking is a bit different from what you’re used to since the plugin uses the query context it’s been given to look up a particular action).
  2. In the above code example, we “injected” the post context (remember, we’re trying to figure out “is THIS particular user allowed to edit THIS particular post) by first fetching it from the database and calling an instance query (this is a feature of Objection.js), where basically the query is being called on behalf of the particular Post model instance. There’s other ways to check this as well, however.

Say I want to edit all posts (for simplicity’s sake). Then I can check whether I’m allowed to edit each and every one of them using a query like this (and remember, you’re trying to let the user edit all of the posts, so all we’re doing is checking the validity of the query itself):

await Post.query().patch(jsonbody).authorize(req.user).fetchResourceContextFromDB()

This will load the posts that are about to be affected by this query (again, this is something provided by Objection’s static hooks) and check your authorization decision against every one of them (and if you wanted, you could authorize N users against M posts as well).

@JaneJeon
Copy link
Author

As for the particular query you asked about (https://github.com/JaneJeon/blink/blob/master/routes/api/users.js#L8), let’s first disregard the authorization and see what the query is trying to do.

The user is trying to read a certain set (max 15, this is a paginated query) of links. You could also just call .fetchResourceContextFromDB(), but either way, you’re going to have to make the call to the DB in order to check “is User X allowed to read Link Y”, so in GET requests like those, you have an additional option of taking the result of the query and checking it synchronously using .authorizeRead(req.user) (which will also automatically strip out any fields that you’re not authorized to read according to the policies).

@JaneJeon
Copy link
Author

And as for your final question, yes, the plugin is intentionally strict in its query checking because it is meant to be used in isomorphic environments. In other words, you’re meant to take the same policy file and check whether a user is allowed to take a particular action in the frontend before you send the request to the backend, and the backend re-uses that same policy file to double check the input (because you can never trust user inputs).

In other words, if a user actually had tried to take an action that they weren’t allowed to take, they would’ve been stopped AT the frontend, and the plugin is there just to ensure that AND “fail fast” rather than wasting resources.

@JaneJeon
Copy link
Author

TL;DR: it is somewhat similar to casl-mongoose/prisma, and yet different in its purpose to drive isomorphic authorization logic and use existing query context to make authorization decisions, so it does more than just checking its given inputs against a given policy - it already KNOWS which policy and which action you’re trying to take (however, note that it is not “magic”, as you can specify literally every part of the authorization decision within the query - the action, the user, the resource, and additional context).

@stalniy
Copy link
Owner

stalniy commented May 10, 2021

OK, just to double check, if I have such policy

allow('read', 'User', { id: user.id })

And then want to authorize a list of users:

const users = await User.query().authorize(req.user)

What will I happen if there are 1000 of users in db?

@JaneJeon
Copy link
Author

JaneJeon commented May 10, 2021

Ah, so you’re worried about the potential impact of the DB. No worries there.

  1. The query that you wrote would literally fetch all of the users in the DB. If you want that, sure. If you want to check the authorization gainst individual user, you could do .authorizeRead() on the resulting set.
  2. The plugin doesn’t “by default” fetch all of the resources about to be affected by the query - it is an EXPLICIT call using .fetchResourceContextFromDB() - so in this case, it would just check if the req.user had the access to read a User in general, without making additional DB calls

TL;DR: it would check allow(‘read’, ‘User’)

@JaneJeon
Copy link
Author

JaneJeon commented May 10, 2021

.fetchResourceContextFromDB() is really meant for cases where you’re doing a PUT request, for example, and you need to check what the user says is the modified model instance versus what is ACTUALLY in the database. In cases like that, you would need to actually fetch the record from the database.

The plugin also provides .diffInputFromResource() method to automatically, well, diff those two objects and only check the authorization against only the fields that changed.

@stalniy
Copy link
Owner

stalniy commented May 10, 2021

OK, I see. Thanks for the explanation!

After some thinking, I realized that there may be other plugins for CASL, I'm and others are not aware of. So, I think a better strategy would be to ask package authors to add casl keyword inside package.json. So, then it's easily searchable on npm -> https://www.npmjs.com/search?q=keywords:casl . This is how mongoose does this and it makes sense for me as well.

I'll create a page in docs that explains that.

@JaneJeon
Copy link
Author

@stalniy personally, I’m much more of a fan of using this method: http://vincit.github.io/objection.js/guide/plugins.html#_3rd-party-plugins

If others thought there were a casl library that people should know about, I think they would’ve created an issue/PR like I have. Plus, in the docs section the presence of “Official Packages” implies the existence of “Unofficial Packages”, and yet people who are reading the docs have no way of knowing which unofficial packages exist and what they do (which is what the “3rd party plugins” section of Objection’s documentation does).

Is that something you would be willing to consider?

@JaneJeon
Copy link
Author

JaneJeon commented May 10, 2021

@stalniy also, it would be especially helpful if you had different sections for 3rd party plugins, basically grouping them generally in terms of what they do (e.g. “frontend plugins”, “backend plugins”, “CASL query transformers”, etc).

Basically, I would like people to know that, like casl-mongoose and casl-prisma, if they want to use CASL in the frontend and the backend, they also have the option in form of objection-authorize (think of how much time someone would waste if they wanted to use casl & objection, but didn’t know objection-authorize existed so they hand-crafted every query and authorization check in a non-isomorphic way). I want people to be able to use authorization decisions in an isomorphic manner, no matter the backend they use!

@stalniy
Copy link
Owner

stalniy commented May 12, 2021

@JaneJeon I added a note about 3rd part packages.

I'm sorry but I don't want to maintain that list. Once somebody adds something to my code, I will need to support it. When amount of that packages grow and everybody will want to put own package in docs, it will become a nightmare for me because:

  1. Review that PRs
  2. Ensure that packages are alive and remove if some of them become abandoned

I think that I can spend my time more effectively :)

@JaneJeon
Copy link
Author

Well, I’m not sure that maintaining 6 packages on your own when you don’t have the time to respond to a simple issue is a “more effective” use of your time (esp. since Objection.js just pushes that PR reviewing/ensuring packages are alive part to the community, because users will let you know if a package is abandoned), but if you want to go that route, I wish you the best of luck.

@stalniy
Copy link
Owner

stalniy commented May 12, 2021

Ouch, not nice :)

Repository owner locked as off-topic and limited conversation to collaborators May 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants