Deleting an access control document correctly

I am looking for advice on how to correctly delete (tombstone) a document, which is also used to grant channel access.
Here’s what I am seeing:

  1. CB Lite creates a document and pushes it
  2. my sync function does essentially 2 things:
  • assign the document to a new channel, which is derived from the doc ID: channel(chnBasedOnDocId)
  • grant access to the new channel: access(“role:X”, chnBasedOnDocId)
    so far so good, all users of that role now have access to the document.

Later I’m deleting the document. My sync function assigns the tombstone to the same channel as before.
But now the tombstone won’t replicate to the users. Their CB Lite instances continue to have the last (pre-tombstone) revision of the document.

I think, the doc deletion has the effect of removing channel access from the role, and therefore subsequent pull replications by other users won’t see the tombstone. Does that sound right ?

How do you suggest I solve this ? I am considering to assign all deletions to a global channel that all users have access to. But is that secure and scalable ? Or is there a better solution ?

Hmmm…the deletion of document shouldn’t change the role access. What does your sync function look like?

In the sync function I am assigning channels based on my DocType property. I am showing only what’s relevant for the PatientPii DocType. You’ll see that each document is assigned into its own channel (“PatientPii_”+DocumentId), and that channel is granted to “role:integration_test”. It works fine until I delete the PatientPii document.
I found that the tombstone revision cannot be in that same channel. Instead I have to assign it into a global channel, which all users have access to. Earlier I assigned the tombstone also into the channel “PatientPii_”+DocumentId, but then the deletion was not pulled to other mobile devices. Assigning the tombstone revision into GlobalCh works. I was just wondering if this is the correct solution. Here comes the sync function:

       function(doc, oldDoc) {
      
        ///////////////////////////////////////////
        function getChannelsForDoc(doc, isDeletion) {
          // TODO RJG 2019-09-20: experimenting with a solution for better test data cleanup. Dealing with this issue https://forums.couchbase.com/t/deleting-an-access-control-document-correctly/23019
          if (isDeletion){
            return "GlobalCh";
          }
          switch (doc.DocType){
           //...
            case "PatientPii":
              return "PatPiiCh_" + doc.DocumentId;
           //...
            default:
              return null;
          }
        }
        //....
        function grantChannelAccessForIntegrationTestApps( doc, channelToAssign ) {
          // we grant access to this new patient's channel to every account with the role "integration_test"
          if (doc.DocType == "PatientPii") {
            access("role:integration_test", channelToAssign);
          }
          //....
        }
        // handle incoming docs through CB Lite push replication
        if (doc._deleted && oldDoc == null){
          throw({forbidden: "Assertion Failure: (doc._deleted && oldDoc == null) should never happen" });
        }
        var docToVerify = oldDoc;
        var isNewDoc = false;
        if (oldDoc == null) {
          docToVerify = doc;
          isNewDoc = true;
        }
        
        var isDeletion = false;
        if (doc._deleted){
          isDeletion = true;
        }

        channelToAssign = getChannelsForDoc( docToVerify, isDeletion );
        grantChannelAccessForIntegrationTestApps( doc, channelToAssign );
        if (channelToAssign != null) {
          channel(channelToAssign);
        }
        return;
      }

Earlier I assigned the tombstone also into the channel “PatientPii_”+DocumentId, but then the deletion was not pulled to other mobile devices.

Ah…I see. You are assigning a user to a role and a role to a channel that in turn contains a single document. When you delete the document, the role loses access to the channel and consequently the user loses access to the channel. That’s why the user doesn’t the deleted document.

What is the access model that you are looking for ? What is the role achieving in your use case? Why do you need a separate channel per document (of say type A) if your role anyway grants access to all channels that hold documents of type “A”.

Simpler model would be to put all type A documents in a single channel for “typeA” and grant users access to this channel. In this case, when a document is removed from channel, the user will still have access to the channel and consequently will see the deleted document.

The access model looks like this:

The mobile app uses Couchbase as a record-keeping system. Users are essentially administrators who are authorized to see the records of many individuals. Each individual’s records are assigned a channel that is specific to that individual. Administrators are users in the system. And each admin’s account has rights to a subset of individuals’ channels.
As records for new individuals get inserted, the system has to grant new channels to existing admin accounts.
And when the records for an individual get deleted, the system has to remove the channels from the admin accounts accordingly.
role:integration_test is a shortcut I use for test automation. With this ticket I wanted to get confirmation that I understand the Sync Gateway behavior correctly.

If you have suggestions for a better channel design, please let me know.