MongoDB Collaborator Permissions

Hi All,

I apologize if this is in the wrong category. I am relatively new to MongoDB and coding in general. The app (using node.js and Swift Realm Drivers) that my partner and I are working on requires the collaborator permissions ability that is listed under the Mongo Documentation → Device Sync Permissions Guide → Dynamic collaboration.

We have managed to implement it as described in the guide and it works when you have a single role with the $or function for the owner and collaborators, i.e.:

{
  "$or": [
    {
      "userId": "%%user.id"
    },
    {
      "invitedUserIds": "%%user.id"
    }
  ]
}

However, what we found is that using this approach, you can only set the same permissions for collaborators and the owner, since they are under the same permissions set. When we try to create a second role with a separate set of permissions, i.e. one for owner and one for collaborator, and set them up such that one contains the owner read/write filter and the other contains the collaborator read/write filter, it doesn’t seem to grant access for collaborators.

Owner Role read/write filter:

 {
      "userId": "%%user.id"
 }

Collaborator Role read/write filter:

{
      "invitedUserIds": "%%user.id"
}

Is this because Atlas is setup to interpret arrays of collaborators with that $or function? Is there a way that we can set owner permissions and also provide an array of collaborators in a separate role with separate permissions from the owner of the document?

Thanks for any help in advance.

Hi @Texpert , and welcome to the community!

Before going into my recommendation, I would first suggest taking a look at our docs on role order evaluation if you haven’t already. Do both roles have the same Apply When expression? If that’s the case, the set of permissions in the first matching role will always apply, regardless of whether a second matching role is defined.

If I’m understanding your use case correctly, you’re looking to create two roles:

  1. Collaborators, which should be able to read/modify documents where their ID is in the list of invitedUserIds
  2. User Owners, which should be able to read/modify documents where their ID matches the userId of the document

I would recommend attempting to leverage custom user data in your role’s Apply When expression. TLDR custom user data is metadata you can store that is tied to your users in addition to the data that App Services stores by default. In this case, you could assign each user a type (either “collaborator” or “owner”), and store that as a field in the custom user data.

Then, you could have a role for collaborators like:

{
    "name": "collaborator",
    "apply_when": { "%%user.custom_data.type": "collaborator" },
    "document_filters": {
         "read": { "invitedUserIds": "%%user.id" }, 
         "write": { "invitedUserIds": "%%user.id" }
    },
    // other permissions  
}

And one for user owners like:

{
    "name": "owner",
    "apply_when": { "%%user.custom_data.type": "owner" },
    "document_filters": {
         "read": { "userId": "%%user.id" }, 
         "write": { "userId": "%%user.id" }
    },
    // other permissions  
}

Let me know if that helps,
Jonathan

Hi @Jonathan_Lee. Thanks for the detailed information! Apologies, I probably need to share a bit more information for this to make sense. We unfortunately can’t use the custom user table because the concept we want to create requires our permissions to be document based, not user based. In other words, a user can be the owner of their own documents and a collaborator on documents that other users own.

Long story short: our app basically has a few different collections, but for the sake of simplicity, let’s use 2 collections and call one of the collections “main” and the second collection “invitations”. The workflow is as follows:

  1. An owner can create a document in the “main” collection via our app front end. This associates it to the field “userId” (which we probably should just rename to ownerId eventually).
  2. Then the owner can send invitation to another user from the front end of our app (user search/query is to be implemented, but that’s a separate issue we’re working through with the documentation). This creates a document in the “invitation” collection - which contains a reference to the document in the “main” collection, similar to a foreign key (we’re using objectId of the document in the “main” collection), as well as the userId that the owner invited, and a field to track the state of the invitation… among a few other irrelevant fields.
  3. The user who was invited will see this invitation and can accept it from the front end, which changes the invite state
  4. This triggers an Atlas trigger function, which inserts the UserId of the individual who accepted the invite into the collaborators array “invitedUserIds” of the owner’s document to grant them access to the document in the “main” collection.
  5. From here, we would expect that the document would then be visible for both the owner and the invited user.

Note: The permissions I’m referencing where we have the issue are applied to the main collection. No issues with invitations since both the owner and the invited user should always have access to this. Both collections use Device Sync/Realm between Node and Swift for real time synchronization.

So, as mentioned, when we set it up exactly as described under a single role, it works, but we can only apply 1 set of permissions. When we set it up with 2 roles (ordered as you reference), where we set the owner first (0) and the collaborator permissions second (1), it does not pick up the second set of permissions, even if the user is not an owner of the document (meaning that it won’t trigger the second set of permissions). This is the setup we tried where it didn’t work (same apply where expression - blank).

Setting this up for Role 0 - apply when blank, read/write filters as follows:

{
      "userId": "%%user.id"
 }

Setting this up for Role 1 - apply when fields blank, read/write fields as follows:

{
      "invitedUserIds": "%%user.id"
}

When we set them up separately, however (i.e. only having role 0 and testing with each individually… they work fine). The problem is when we do it with role 0 and 1, where 0 has full permissions as the owner, and the 1 has document level field permissions, search only, no insert, no delete, the documents in the “main” collection don’t return from Realm for the collaborators.

I apologize if this isn’t so clear.

TLDR: When using permissions with 2 roles (role order evaluation) with different permissions for owners/collaborators, collaborators can’t see the collection. Is this due to the specific field level permissions we set by chance? When setting them up individually OR with the $or method as described in the documentation, it works perfectly fine.

Thanks again for your help. Much appreciated.

@Jonathan_Lee - I tried implementing a match condition on Apply When that matches the same statement as my document filter:

{
      "invitedUserIds": "%%user.id"
}

, but it doesn’t seem to work or evaluate the Apply When expression. I tried it with document filters set to true for read/write as well, but still it returns nothing. Is this Flexible Sync compatible?

Hi,

For usage in Flexible Sync, a role’s Apply When expression cannot reference document fields. The reason for this is because in Flexible Sync, roles are evaluated at session start (before any documents are looked at), as opposed to on a per-document basis. You can find more information about this behavior here.

The issue with having an empty Apply When expression for both roles is that an empty Apply When expression always evaluates to true. Thus, due to the nature of role order evaluation, the permissions described by the first role will always come into play.

Thus, in this case, for any sync session, the permissions set described by role 0 would always be applied for the course of a sync session. For a user trying to sync, documents that matched the expression:

{
      "userId": "%%user.id"
}

would be visible to the user, which explains why the collaborators wouldn’t have been able to access even documents such that their ID appears in “invitedUserIds”.

That’s the main reason why I was suggesting trying to incorporate custom user data into your permissions evaluation strategy (this is commonly how I’ve seen other apps setup Flexible Sync permissions in a more dynamic way). Also, if I understand correctly, if you don’t need restrictive permissions on the fields in the document, you could combine both roles into a single role like:

{
    "name": "role",
    "apply_when": {},
    "document_filters": {
        "read": { 
            "$or": [
                { "userId": "%%user.id" },
                { "invitedUserIds": "%%user.id" }
            ]
        },
        "write": { 
            "$or": [
                { "userId": "%%user.id" },
                { "invitedUserIds": "%%user.id" }
            ]
        },
    },
    "search": true, 
    "insert": { "userId": "%%user.id" },
    "delete": { "userId": "%%user.id" },
    "read": true,
    "write": true
}

(You can see the full JSON view of the role by clicking the “Advanced View” in the toggle in the roles viewer)

Screenshot 2023-08-16 at 6.25.45 PM

As for why this only works if you don’t need field-level permissions – this is because dynamic expressions for field-level permissions is currently unsupported in Flexible Sync.

Alternatively, if using the permissions system in a way that fits the needs of the application seems impossible, you could always edit your application logic to perform these kinds of checks separately.

Let me know if that helps,
Jonathan

Hi @Jonathan_Lee

Thanks for the reply and clarification on this. Appreciate the full explanation. It makes sense why it is not working when apply when is empty and also unfortunately we do need field level permissions for collaborators, but full access to the document for our owners, both of which being document specific. I had a feeling that perhaps it wasn’t supported yet and we figured we could edit our application to create our desired behavior as a last resort, but we figured we’d try to see if we could restrict data access at the DB level, if at all possible. Do you know if there are plans for Mongo to support dynamic permissions w/ flexible sync and field-level restrictions in your roadmap? I think it would be pretty powerful for not just us, but perhaps many other apps.

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.