Tracking customer-visible content in Confluence with ScriptRunner

I'm still figuring out SR for Confluence, and I'm just wondering if something I want to do is a dead-end or not (before I invest too much time on it).

One of the biggest issues we run into is auditing the visibility of content in Confluence. As a very simple, anybody-can-check auto-audit, I was thinking about making my first real exercise with SR4C to be setting up a routine that checks for pages visible to external user accounts and either adds a panel, footer, or label to the page saying so.

All of our "internal" accounts belong to a common group (let's call it 'internal-users'). This scripted behavior would look for any and all pages that can be successfully loaded by even a single user account that is not a member of 'internal-users'. If it can be, then let's say it adds a "customer-visible" label to the page.

Likewise, in reverse, if the security is changed and a visible page is restricted to internal-only, the "customer-visible" label is removed.

Is this a feasible thing to pursue? Is there maybe a simpler approach? I'm basically ruling out anything that requires mass education of our user-base.



11 answers

For the basic implementation discussed above (showing a banner or warning panel indicating that the page is by some means public) the Script Fragment shown below should work. I used the Web Panel type, since we don't want this buried in a menu.

One big caveat is that this script does not check for anonymous access, which should probably be done as well for any real implementation.

Example script fragment implementation:

Screen Shot 2016-07-19 at 11.26.38 AM.png

package com.onresolve.examples

import com.atlassian.confluence.core.ContentPermissionManager
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.plugin.descriptor.web.DefaultWebInterfaceContext
import com.atlassian.confluence.user.UserAccessor
import com.atlassian.sal.api.component.ComponentLocator

String internalUsers = "internal-users"
Closure otherGroupsTest = { groupName ->
    groupName = groupName.toString()
    !([internalUsers, "confluence-administrators"].contains(groupName)) //list any internal-only groups here
def userAccessor = ComponentLocator.getComponent(UserAccessor)

DefaultWebInterfaceContext context
def page
if (this.binding.variables.context) {
    context = context
    page = context?.contextParameters?.page
    if (userAccessor.getGroupNames(context.currentUser).any(otherGroupsTest)) {
        return false; //Don't show the banner to external users; they know what they can see. :)
else {
    //This block was entirely for debugging purposes, so I could run the script via the console
    //In an actual fragment, the context variable would always be defined
    context = new DefaultWebInterfaceContext()
    def pageManager = ComponentLocator.getComponent(PageManager)
    page = pageManager.getPage(98312)

if (!page) {
    return false

Closure checkPermissionSets = { ContentPermissionSet permissionSet ->
    boolean anyGroups = permissionSet.getGroupNames().any(otherGroupsTest)
    boolean anyUsers = permissionSet.getUserKeys().any { userKey ->
    anyGroups || anyUsers

def contentPermissionsManager = ComponentLocator.getComponent(ContentPermissionManager)
def pageLevelRestrictions = contentPermissionsManager.getContentPermissionSets(page, ContentPermission.VIEW_PERMISSION)
if (!pageLevelRestrictions.isEmpty()) {
    def anyPageLevelRestrictions = pageLevelRestrictions.any(checkPermissionSets)
    return anyPageLevelRestrictions //Since there are view-level restrictions, they should win out over any space perms

def spacePermissionManager = ComponentLocator.getComponent(SpacePermissionManager)

def spaceGroups = spacePermissionManager.getGroupsForPermissionType(SpacePermission.VIEWSPACE_PERMISSION,
def anyGroupsExternal = spaceGroups?.keySet()?.any(otherGroupsTest)

def users = spacePermissionManager.getUsersForPermissionType(SpacePermission.VIEWSPACE_PERMISSION,
def anyUsersExternal = users?.keySet()?.any{ userName ->

anyUsersExternal || anyGroupsExternal

Thanks for so much effort! I did run into one place where it seems to break at line 50. A static type checking error is returned by SR when it tries to use getContentPermissionSets(). Here's a quick grab:


That trickles down into errors for most of what follows. I can't personally determine the problem, since everything looks fine as far as the documentation for my version of Confluence (5.9.3) seems to suggest.

What confluence version are you using?

At first glance I would say that's feasible... I think the issue might be checking the permissions for "any user not in your group". If it was a case of checking whether "external group" had access that would be easy, but any user not in a group... off the top of my head I'm not sure if that can be done in an efficient way. The mechanics of how you display it is probably not a problem, just see what works best.

I'll get back to you on Sunday or Monday...

Probably not opposed to having an 'external-users' group. It's been talked about before, but JQL has supported the 'not in membersOf()' method without issue, so nothing really made it happen.

On the other hand, we're adopting Crowd soon, and every internal account will be switched over to an LDAP directory, while external accounts won't be. So that whole grouping thing might suddenly be a lot simpler.

Are you interested in the space permissions, or page permissions, or both combined? I take it you want the info on the current page, and about the current page, rather than some kind of report?

You can ignore the first part, I re-read your question and I think you want both.

OK the way I would do this is examine the VIEW_SPACE perms, and then remove any users/groups that are not included in page restrictions, if there are any (inherited or otherwise).

From that list, if there are any groups there other than internal-users it's publicly visible. If there are any users with view perms, that are not in the internal group, it's publicly visible. Otherwise, it's not.

Does that logic make sense? If so, we can help you write that.

...both...combined...probably. An unrestricted page isn't necessarily customer-visible, if the space itself doesn't have any customers on its access list. I guess it would step through it: (1) if the space grants access to 'external-users', then (2) if the page doesn't block them from viewing it.

A lot of leads expect to have space admin rights, so I'm working with the assumption that this will have to deal with the fact that any random lead might add an external user individually or as a group to their space access list.

If a report is the easiest way to do it, I wouldn't be averse to a report-per-space. Using labels just struck me as the more versatile approach, since it makes tagged pages query-able.

Okay, since I don't feel like this is hopeless to try, I'm going to take some time and see if I can do it before I spend up yours. =) If I can't pull it off in a day or two, I'll come back for more help. If I succeed, I'll do the honorable thing and post my solution, then just close this out. XD

sure... I would not use a label though. A label would be nice because it would mean you could find these pages quickly, but that would involve listening for all permissions events, then potentially doing large bulk changes. Instead I would go for a web panel that shows the message... at least for v1.

After fleshing this out more, people lost interest in using panels or labels. The wish now is for some kind of easy-to-review report about what each user account is able to see (without having to SU one account at a time). That's probably well beyond the design of ScriptRunner here, or at least too much work compared to just directly querying the database. :/

It's a shame about the labels/panels, because I think that is a very good use case. IMHO people should know it's public at the time of editing, not have to run a report. We use something similar to the original idea in our JIRA instance, and it works well.

In terms of SR I would do it as a report that you run in Script Console... it's exactly in the sweet spot for SR. The database route might be tricky when dealing with inherited perms. Also db changes are harder to cope with than API changes.

Let us know how you go, we are happy to do an outline of the script if you choose that way.

Personally I would prefer the original approach still. I'm curious about the setup you're all using. We have a long wishlist of security-related changes people want to see out here. Since you can never prevent all possible brain-farts, I like the idea of approaching this stuff by improving the visibility of a setting rather than implementing complex restrictions and processes.

And honestly, Confluence page security isn't the most intuitive thing in the world for most users.

If you have an outline in your head for the labelling/panel approach, I'd appreciate the assist. I'd prefer a preventative solution (like this) before a reactive one (reports).

Suggest an answer

Log in or Sign up to answer
Community showcase
Published Mar 12, 2019 in Confluence

Confluence Admin Certification now $150 for Community Members

More and more people are building their careers with Atlassian, and we want you to be at the front of this wave! Important Dates Start the Certification Prep Course by 2 April 2019 Take your e...

1,017 views 2 13
Read article

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you