Hello, I am trying to define Rules in my app service for the following use case:
I want to limit the pool of documents in a collection that each user can access. I have set up custom user data for each user. One of the fields in the custom user data is an array with the ids of the documents that the user can access from the collection. My attempt to define Document Permissions on the collection seems to not be working:
{
"_id": {
"$in": "%%user.custom_data.allowed_ids"
}
}
Any ideas?
Hi,
Do you mind sending me your app URL (looks like https://realm.mongodb.com/groups/<group-id>/apps/<app-id>
) so I can take a further look? For context, we recently changed where and how flexible sync permissions are defined for new apps, and I’m interested in seeing if your app was affected by that change at all.
Thanks,
Jonathan
Hey @Damian_Danev,
I took a look at your app, and didn’t find anything immediately suspicious. Based off the way that you described it, I’m assuming that the above expression corresponds to the “Read Document Filter”. What was the specific behavior that you were seeing that caused a problem? Were there documents that were being sent down to the client that were not expected? Was there an error that your app was running into?
A few things I’d also like to confirm:
- In the custom user data, could you make sure that:
a. The linked user id field is a string
b. The allowed_ids field contains a list of ObjectIDs
- (If there are multiple roles defined in the rule) The role containing the above expression is being applied (we log which role is applied during a sync session, see below screenshot)
- The role does not inherit write access (ie, the “Write Document Filter” does not evaluate to true in the sync session)
Jonathan
Sorry, I should’ve been more specific.
This is a document inside collection plots:
{
"_id":{
"$oid":"63fe58578fd1b60e9636478a"
},
"name":"asdnd",
"gro_id":"63fb8bebe2f721ef732ad540"
}
This is a document inside the custom user data collection called users:
{
"_id":{
"$oid":"63fa9a794c4b020beaa5ff86"
},
"user_id":"63fa9a794c4b020beaa5ff81",
"email":"someemail",
"role":"scout",
"user_ids":[
""
],
"plots":[
"63fe58578fd1b60e9636478a"
]
}
This is a Rule I’ve applied to plots collection:
{
"roles": [
{
"name": "scout",
"apply_when": {
"%%user.custom_data.role": "scout"
},
"document_filters": {
"write": {
"_id": {
"$in": "%%user.custom_data.plots"
}
},
"read": {
"_id": {
"$in": "%%user.custom_data.plots"
}
}
},
"read": true,
"write": false,
"insert": false,
"delete": false,
"search": true
}
]
}
I just want users of a certain role to only be able to read documents whose ids are inside a field in the user’s custom data. With this setup, I do not get any documents on my React Native realm. I think I might be failing on the point you made about object ids being a string because I am comparing it to the plot object id:
"_id":{
"$oid":"63fa9a794c4b020beaa5ff86"
},
Yeah, I think converting plots
in the custom user data collection to be a list of ObjectIDs would potentially resolve the issue then. Let me know if that works!
I managed to solve the problem with the plots rules using strings only.
Now I have a similar problem with just the users collection. I want realm clients to be able to read user documents based on a list of user ids I provide for them in the same collection (user_ids)
Example:
inside the users collection I have these two users:
{
"_id":{
"$oid":"63fb8bebe2f721ef732ad540"
},
"user_id":"63fb8bebe2f721ef732ad534",
"email":"manager",
"role":"manager",
"user_ids":[
"63fa9a794c4b020beaa5ff81"
]
}
{
"_id":{
"$oid":"63fa9a794c4b020beaa5ff86"
},
"user_id":"63fa9a794c4b020beaa5ff81",
"email":"intern",
"role":"intern",
"user_ids":[]
}
I want to make a rule so that a manager can only read intern users that are associated with him through the user_ids field. (I am also open to other suggestions). Just in case it’s not clear: users is the custom user data collection that’s linked with the app users. user_id is the linked field that holds the id of the app user and is also a queryable field.
I thought this would be the correct rule:
{
"roles":[
{
"name":"manager",
"apply_when":{
"%%user.custom_data.role":"manager"
},
"document_filters":{
"write":{
"user_id":{
"$in":"%%user.custom_data.user_ids"
}
},
"read":{
"user_id":{
"$in":"%%user.custom_data.user_ids"
}
}
},
"read":true,
"write":true,
"insert":false,
"delete":false,
"search":true
},
]
}
But my mobile app just gets stuck loading when I want to sync as a manager user.
When I set the document rule to:
{
"roles":[
{
"name":"manager",
"apply_when":{
"%%user.custom_data.role":"manager"
},
"document_filters":{
"write":{
"user_id":{
"$in":["63fa9a794c4b020beaa5ff81"]
}
},
"read":{
"user_id":{
"$in":["63fa9a794c4b020beaa5ff81"]
}
}
},
"read":true,
"write":true,
"insert":false,
"delete":false,
"search":true
},
]
}
Then the app functions properly, but when using "$in":"%%user.custom_data.user_ids"
then it doesn’t work. Any idea why?
Okay. I think I found out what the problem was, even tho the solution puzzles me…
I had to add user_ids
as a queryable field
.
@Jonathan_Lee , A new problem arouse. The _id
of my custom user data collection is created as an ObjectId but it seems to me that it is passed as a string in the rule expression:
{
"owner_id": "%%user.custom_data._id"
}
This doesn’t return any documents even tho the ids match (owner_id
is an ObjectId as well). Further when I created a new field _oid
and made it the exact same ObjectId as _id
then this expression works and returns the correct documents:
{
"owner_id": "%%user.custom_data._oid"
}
What is happening? This smells of a poor design or maybe I am not understanding something. Is there a way to cast ObjectId to string and string to ObjecId inside the expression of the rule?
PS: I found %stringToOid
and %oidToString
Hi,
What is happening? This smells of a poor design or maybe I am not understanding something. Is there a way to cast ObjectId to string and string to ObjecId inside the expression of the rule?
Our system is designed to allow usage of custom user data in functions, which requires us to convert that data into js values. ObjectIDs are mapped to strings in this process, and the _id
field is treated as a string under-the-hood for the purposes of compatibility.
PS: I found %stringToOid
and %oidToString
You should be able to use %stringToOid
like:
{
"owner_id": { "%stringToOid": "%%user.custom_data._id" }
}
Alternatively, you could go with the other approach you mentioned or by using the “user_id” field with "%stringToOid"
instead.
Also, regarding the previous comment about user_ids
– could you elaborate on the behavior you were seeing before adding user_ids
as a queryable field? Were there any errors in the logs? It seems pretty unexpected to me that user_ids
would have to be added as a queryable field to resolve the issues you were encountering, so any additional information would be helpful in diagnosing the situation.
Jonathan
You should be able to use %stringToOid
Yes, but cases like looking for the object id in a list of object ids doesn’t work:
{
{ "%stringToOid": "%%user.custom_data._id" }: { '$in': 'user_oids'}
}
Unfortunately, I can’t state an exact problem. I am experiencing weird stuff with Rules
and I hope it’s only because I am running on the free tier. It’s an important IF because this is scary in production. After changing the rules I wasn’t able to sync until I added user_ids
as a queryable field. Often I have trouble syncing after making changes to the rules. Sometimes I have to wipe device data and start of a fresh realm. One time I made changes to the Rules
, and some documents weren’t syncing, made an exact copy of them and the new ones got synced. My brain started hurting, took 2 hour’s break. When I came back everything was working magically without any changes. If I have to be honest changing the Rules
scares me. I often am not able to sync back in the mobile app. I am considering leaving the Rules
more open so that in the future I don’t have to do a lot of changes to them in production and possibly run into unsuccessful client resets. I hope this is happening only because I am on the free tier and things take some longer to happen at times.
Hi Jonathan, looks like there are other ways. I got it working by doing this:
My Rule on Account Collection:
owner_id is of type ObjectId
{
"owner_id": {
"%stringToOid": "%%user.id"
}
}
But to get this to work in React I needed to this in my update function:
const { _id, owner_id, ...restofAccount } = draftAccount
await accountItemCollection.updateOne(
{ owner_id: { $oid: app.currentUser.id } },
{ $set: { ...restofAccount } }
)
Before, when I had the owner_id as String I was able to use it like the documents say, and I thought this kind of magic happened in the background
const { _id, owner_id, ...restofAccount } = draftAccount
await accountItemCollection.updateOne(
{ owner_id: app.currentUser.id },
{ $set: { ...restofAccount } }
)
So I wonder if this is the correct way of handling this.
Cheers Cato
Update: this is probably the corrected way of sending a ObjectId object:
const { _id, owner_id, ...restofAccount } = draftAccount
await accountItemCollection.updateOne(
{ owner_id: new BSON.ObjectId(app.currentUser.id) },
{ $set: { ...restofAccount } }
)
1 Like