Repository navigation
Data fetching (REST) with any user-permissions relations does not work (as if ignored completely) #17001
Description
Bug report
Required System information
- Node.js version: 18.14.0
- NPM version: 9.3.1
- Strapi version: 4.11.1 (but this seems to be the problem for longer time)
- Database: PostgreSQL
- Operating system: MacOS Monterey
- Is your project Javascript or Typescript: Javascript
Describe the bug
I have multiple DB tables that have "user" fields which are relations to users-permissions.
All tables with issues have this relation:
"user": {
"type": "relation",
"relation": "oneToOne",
"target": "plugin::users-permissions.user"
},
This tables are for example orders on the web-shop, service records for devices and so on. All related to logged-in users.
I discovered that filtering by these relations are not working at all anymore and all my registered users could see ALL orders - good thing that I still have small number of registered users, but I presume, someone could have the same issue on very large system!
Also - population does not work on same fields - if I use "populate=*" or "populate[0]=user" in query - this does nothing.
I noticed that all other relations seem to work - filtering + population. So it seems, only relation to users-permissions is broken.
But this is quite a big issue.
I need to note that these queries did not change for over 8 months in my code so I did not even have this on the radar that it could be a problem. Now I need to write some new tests.
Steps to reproduce the behavior
Simply try REST query on the table where you want to filter by related ID - for example, if I execute this when using 4.11.1:
http://localhost:1337/api/orders?filters[$and][0][user][id][$eq]=30&sort[id]=DESC&populate[0]=user&filters[id]=366
I get all orders and I should only get the ones related to user with ID 30.
Also - none of the records have "user" populated in response.
Same thing happens on all my tables with relation to users-permissions.
Expected behavior
Filtering by related table should work.
Population should work
Code snippets
By dumping ctx.request.query into my log - I can see that REST query seems correct:
[2023-06-13 20:40:30.095] http: GET /api/orders?filters[$and][0][user][id][$eq]=30&sort[id]=DESC&populate[0]=user (171 ms) 200
[2023-06-13 20:40:35.580] orders-find-query {
"filters": {
"$and": [
{
"user": {
"id": {
"$eq": "30"
}
}
}
]
},
"sort": {
"id": "DESC"
},
"populate": [
"user"
]
}
Additional context
I went back to vesion 4.6.2 just to test - and there the filtering works but population does not. I did not go back to where population works also.
If seems that from version 4.8.1 both filtering and population on these relations does not work. So, as said, this seems to be an issue for longer time.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsFixed/Shipped
Activity
ntarandek commentedon Jun 14, 2023
UPDATE: I remembered that I've had similar issue with different problem - and it was resolved when I gave the right to "find" on that relation. This is the case here also. When I do that (allow authenticated users), filter works - but population still does not. Only when I also allow "findOne", population works.
This resolved my immediate issue, but we still have a huge problem. When find/findOne is not allowed, result should surely not be ignoring the filter and dumping everything out - should it? I would hugely prefer some error message like "You are filtering by field/relation that you do not have access to" (or similar).
Also, if I allow "find", anyone with valid JWT token can list all users - which is another can of worms, but probably solvable in a different manner (policies maybe) - will look into that.
danielmuxel commentedon Jun 14, 2023
I had to create my own controllers for the content types that had a user relation. In my cases, I needed to give the users the access to view their created content (e.g. their events).
This change came some thing 4.7.x ish. If the fields could be populated user data could be leaked.
For example this controller was for my events that only the users themselves need (api/event/controllers/auth-user-events.ts)
(And yes the code could probably be better)
This would be a router (api/eventsroutes/auth-user-events.ts)
If other users in the frontend would have to search for that user, it is not possible. But if the user also has a open Profile that can be found the base controller filters would work.
ntarandek commentedon Jun 14, 2023
Thank you for the reply @danielmuxel! I am aware of this option, and I already have a lot of custom controllers and methods. This just caught me a bit of guard for simple filtering/population. This is, as it seems, the way ;)
tnavadiya commentedon Jul 10, 2023
Is this resolved? I have two users in User table. And having one user has multiple hotels. I have assigned 2 hotels to one user and 1 hotel to another user. Now I have to fetch a list of hotels assigned to logged in user. How can I do that?
ntarandek commentedon Jul 11, 2023
@tnavadiya As far as I see - when calling via REST API, the issue is still there. But, like @danielmuxel noted, if you do it yourself via extended or custom controller, then you can get the data for specific user.
GregorSondermeier commentedon Jul 11, 2023
The thing is:
The REST API's default controllers use sanitizeOutput() under the hood which I think will remove any private attributes and relations you don't currently have permission for from the output.
That's why if you create a custom controller which uses
strapi.service,strapi.entityServiceorstrapi.db.queryto do your find request, and if you do not callsanitizeOutput()manually, those user relations will be returned.This is somewhat documented here:
https://docs.strapi.io/dev-docs/backend-customization/controllers#sanitization-when-utilizing-controller-factories
Your only option currently is to give the find permission so that the relation does not get thrown away by
sanitizeOutput. Of course this opens a door for people to load all users. You could create a custom policy which prevents direct access to the user.find route for everyone to close that door again.There is currently an intention to completely replace the existing users-permissions plugin. Please leave your upvote here to increase priority:
https://feedback.strapi.io/feature-requests/p/field-level-permissions-end-user-management
ntarandek commentedon Jul 11, 2023
I did not like allowing public find/findOne for users as this is way too big of a security issue - so I used custom controller, since I only need filtering by user, not the user data (by relation) itself - so I do not populate that at all.
But you are 100% correct about data sanitization @GregorSondermeier - if you get the data like this, sanitation is a must.
Not hard to call, but you need to remember it :)
geeky-biz commentedon Jul 18, 2023
For us, upgrading from
4.5.2=>4.10.6caused this issue and resulted in some of our Rest APIs returning a lot more data than they should have. Eventually fixed it the way mentioned in the comments above.Given that this issue causes a filter criteria to be silently removed, it can cause inadvertent leak of data. From that perspective:
severity : lowseems inadequate.10 remaining items
derrickmehaffy commentedon Jul 19, 2023
As a subset to my previous response this request sanitization happens within the controller (core controllers), and generally if you are building a is owner type API you will want to use route policies to strictly enforce what they have access to in addition to the permission set by user and permissions.
Likewise when you have to enable the find or find one permissions you'll want to protect the users and permissions endpoint by creating a policy or a middleware for that.
We could probably construct some of these by defaults and ship them with the application and just allow users to turn them on or enable them but we would need to do a bit of discovery on that.
If you're using the entity service directly then these request sanitization of both filters and population do not apply meaning you can use the entity service within a policy or a route middleware to do accurate comparisons between the request state (user/role id) against the entity that they're trying to perform the action against.
Keep in mind that the construction of these policies is extremely subjective and for is owner-based policies we basically can't build a generic one for everyone, but we could provide sample code that would put you in the right direction depending on the way your content models are structured.
geeky-biz commentedon Jul 19, 2023
We fetch
dealsthat were assigned tousers. Via our middleware, we had added a filter condition(({'users' : {id : {'$eq': id}}}) to return only those deals that were assigned to the requesting user (for/api/deals). This worked perfectly on Strapi4.5.2. Upon upgrade to4.10.6- every user started to see all the deals.I now understand that this happened because we had so far not given this role access to
user.find. We have fixed this by giving this access & blocking theuser.findvia a policy.But, like I mentioned, a data-leak like this can have far-reaching consequences. And, I foresee many others going through an experience like ours.
Boegie19 commentedon Jul 19, 2023
The id is not a private field
alexandrebodin commentedon Jul 19, 2023
FYI we are regrouping early next week to address this topic. Thank you for bringing this to our attention 👍
derrickmehaffy commentedon Jul 25, 2023
Hey all,
We conducted an internal meeting/discussion around this today and came up with a few action items and things we plan to do in the short-term/long-term. This meeting involved myself, @alexandrebodin, @Convly, and @innerdvations .
Short Term
Long Term (TBD)
I want to be transparent and open about the new users-permissions plugin, we do not have a timeline for this yet. I need to speak to @Aurelsicoko as it's a decision that has to come from our product team and not our engineering team and it would be a massive amount of work that we need to justify. With v5 development in full swing our initial v5 development scope is filled and locked so at bare minimum we wouldn't even be able to start this part until after probably Q2 2024.
In summary, we tried to prevent adding breaking changes with security fixes and we inadvertently did so already so we are adding another that would put us back in-line with being "secure by default". What I want to do is once we start working on this default 403 response PR, we will release a few experimental/beta releases and ask to have a few people assist us in testing this out to ensure it meets your expectations. I know it's not a complete fix the problems mentioned here but we will work towards that in the long term.
Also thank you for raising this topic, I would share the recording of our meeting but we discussed several things here and some of them we can't share.
Thanks
ntarandek commentedon Jul 25, 2023
Hi @derrickmehaffy,
I believe that I am not the only one that would prefer to get an access error instead of silent data-leak :)
So, in my eyes, your short-term plan seems like a logical and concise solution to an issue at hand - especially the thing about documenting - since that way everyone can easily resolve this kind of things.
Thank you for taking serious time to find a way to resolve this important issue.
Convly commentedon Jul 25, 2023
Yep, it would probably be a generic 400, not a 403.
But other than that, pretty good summary 👌
derrickmehaffy commentedon Jul 25, 2023
@ntarandek of course, quite welcome 🤗
Transparency and Active care are two of our core company values and we prefer to extend these values to our users and not just internally.
I have a chat with @marcoautiero on Thursday so hopefully I'll have updated timelines then.
derrickmehaffy commentedon Aug 21, 2023
Update on this, we just merged this PR into another branch but we expect this fix to be released in v4.13.0 on Aug 30th.
This will forcefully throw an error if someone attempts to filter on something they shouldn't have access to which is a break change but we have no other choice: #17693
derrickmehaffy commentedon Aug 30, 2023
This was patched in v4.13.1 so I'll go ahead and mark it as closed
LucaNerlich commentedon Sep 3, 2023
The Release Notes mention "REST" only, but all our GraphQL Calls which fetch an image are now broken as well.
Error: Missing schema in validateQueryPublic Endpoint, no user-permissions involved.
Below query breaks as soon as I add
image.Someone else on discord has the same issue: https://discord.com/channels/811989166782021633/1147035990049968199
possible fix: #17895
strapi-bot commentedon Sep 3, 2023
This issue has been mentioned on Strapi Community Forum. There might be relevant details there:
https://forum.strapi.io/t/4-13-1-graphql-error-missing-schema-in-validatequery-for-images/31743/1