How to determine fully shareable status of a ContentEntityObject?
Asked by Carl Nagle:

I currently have an in-house Confluence EventListener add-on that sends content from Confluence ContentEvents to our DevOps Kafka bus. However, we have found that content marked in the UI as Private (or otherwise "not shared") is happily sent to the Listener and we happily send the private content to the Kafka bus for everyone to see. :(

All the references I've seen seem to focus on "does a specific user have permission". However, we need the filter to answer the question "is this shared with EVERYONE", and I have not found an ALL_USERS constant.

Currently, I'm experimenting down a trial-and-error path of something like:

```
boolean isShared(ContentEntityObject content){

? ? return ((content.sharedAccessAllowed((String)null)) &&
? ? ? ? ? ? (content.sharedAccessAllowed((User)null))?  && 
? ? ? ? ? ? (!content.hasContentPermissions()));
}
```

But some documentation suggests restrictive permissions from the Ancestry of the content (parent Pages or Space) are not provided in the List of ContentPermissions provided for the specific instance of content?

Is there more information available for identifying only fully shared content that has no restrictions whatsoever? Or, more precisely, that a specific piece of content (Page, Blog, Comment) has no restriction on it--including inherited restrictions/permission?


Answers:
author: Carl Nagle
body: This is what seems to have worked for me. The following restrictions are filtered and NOT sent to Kafka (or any external destination):

* User UNSELECTS Notify Watchers
* Confluence otherwise sets Suppress Notifications
* Page/Blog is not yet "Published"
* Page/Blog has ANY View Restrictions
* Page/Blog Ancestry has ANY View Restrictions
* Space itself has View Restrictions
* Comments on ANY View restricted Page/Blog are also restricted

This method is called by recursePermissions to determine if the Space itself is Shared or not. Experimentaiont with Space Tools uncovered an issue which is corrected in 'sharedSpacePermissions' below:

```
 private boolean sharedSpacePermission(Space space){
     final String ALL_USERS = "confluence-users";
     if(space.getPermissions() != null){
         for(SpacePermission permission:space.getPermissions()){
             // if VIEW permission is for Group 'confluence-users' it is a Shareable Space
             if((SpacePermission.VIEWSPACE_PERMISSION.equalsIgnoreCase(permission.getType())) &&
                 (ALL_USERS.equals(permission.getGroup()))) 
                 return true;         }
         log.debug("*** Space has no 'confluence-users' VIEW permission so it should not be shared! ");
     }else{
         log.warn("??? Unexpected case of Space not defining ANY permissions. It is not shared. ???");
     }
     return false;
 }
```

And recursePermissions handles the recursive ancestry check for permissions:

```
 private boolean recursePermissions(ContentEntityObject content){
     final boolean shared = true;
     String type = content.getClass().getSimpleName();

     if(content.hasPermissions(ContentPermission.VIEW_PERMISSION)){
         log.debug("*** The "+ type +" contains a VIEW restriction and should not be shared! ");
         return !shared;
     }
     // a Restricted page might have no ContentPermissions--but its Parent Containers WILL!
     if(content instanceof Comment){
         if(((Comment)content).getContainer() != null){
             if(!recursePermissions(((Comment)content).getContainer())) return !shared;
         }else{
             log.warn("??? Unexpected case of a Comment outside of a Container ???");
         }
     }else if(content instanceof Page){
         if(((Page)content).getParent() != null){
             if(!recursePermissions(((Page)content).getParent())) return !shared;
         }else if(((Page)content).getSpace() != null){
             if(!sharedSpacePermission(((Page)content).getSpace())) return !shared;
         }else{
             log.warn("??? Unexpected case of a Page having no parent Page or Space ???");
         }
     }else if(content instanceof BlogPost){
         if(((BlogPost)content).getSpace() != null){
             if(!sharedSpacePermission(((BlogPost)content).getSpace())) return !shared;
         }else{
             log.warn("??? Unexpected case of a BlogPost having no parent Space ???");
         }
     }else{
         log.warn("\n\n\n??? Unexpected "+ type +" not being processed for permissions ???\n\n\n");
     }
     log.debug("*** The "+ type +" did not contain any VIEW restrictions...");
     return shared;
 }

```

And to kick it all off, we first check for suppressed notifications and the like. This is the entry point for the overall "is it shareable" check:

```
 private boolean isShared(ContentEvent event){ 
     final boolean shared = true;
     ContentEntityObject content = event.getContent();
     if(content.isUnpublished()){
         log.debug("*** The content has not yet been published!");
         return !shared;
     } 
     if(event.isSuppressNotifications()){
         log.debug("*** The event SUPPRESSED notifications!");
         return !shared;
     } 
     if(!content.sharedAccessAllowed((User)null)){
         log.debug("*** The content.sharedAccessAllowed((User)null) is NOT shareable! ");
         return !shared;
     } 
     return recursePermissions(content);
}
```


Tags: confluence-server

server